mirror of
https://github.com/SparklyPower/SparklyPaper.git
synced 2025-12-19 15:09:27 +00:00
Update docs
This commit is contained in:
16
docs/BORKED_PATCHES.md
Normal file
16
docs/BORKED_PATCHES.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Borked Patches
|
||||
|
||||
List of *maybe* borked and *maybe* useless patches that I found in other fork's that may be borked or not do anything useful, so I annoted them here to remember about why I didn't cherry-pick them to SparklyPaper.
|
||||
|
||||
Keep in mind that I'm very naive when it comes to Minecraft Server internals, so I may be wrong!
|
||||
|
||||
## (Pufferfish) `Reduce-entity-allocations`
|
||||
|
||||
While not useless, the patch adds a `cachedBlockPos` variable that is never used by any other patch. Heck, not even in Airplane it was used!
|
||||
## (Pufferfish) `Skip-cloning-loot-parameters`
|
||||
|
||||
Unnnecessarily wraps `parameters` and `dynamicDrops` into a unmodifiable map, causing unnecessary allocations.
|
||||
|
||||
This was useful back in 1.17 days, where the patch DID bring a meaningful benefit, since vanilla used `ImmutableMap.copyOf` instead.
|
||||
|
||||
As a reference, here's Airplane's original patch: https://github.com/TECHNOVE/Airplane/blob/af3563c98bdd8b27123e3a656de261ed96652b3e/patches/server/0030-Skip-cloning-loot-parameters.patch#L21
|
||||
135
docs/PARALLEL_INCOMPATIBLE_PLUGINS.md
Normal file
135
docs/PARALLEL_INCOMPATIBLE_PLUGINS.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Plugins incompatible with Parallel World Ticking
|
||||
|
||||
A list of plugins that I found out that causes issues with Parallel World Ticking.
|
||||
|
||||
This is not a full comprehensive list because I only use a few public plugins, most of the plugins I use on SparklyPower are made by myself.
|
||||
|
||||
## NoCheatPlus (Updated)
|
||||
|
||||
The movement checks can crash the server due to concurrency issues (more below), disable them in the config.
|
||||
|
||||
```javastacktrace
|
||||
[03:14:05] [serverlevel-tick-worker-8/ERROR]: THE SERVER IS GOING TO CRASH! - Thread serverlevel-tick-worker-8 failed main thread check: Cannot query another world's (world) chunk (25, 16) in a ServerLevelTickThread - Is tick thread? true; Is server level tick thread? true; Currently ticking level: ArenasPvP; Is iterating over levels? true; Are we going to hard throw? false
|
||||
java.lang.Throwable: null
|
||||
at net.minecraft.server.level.ServerChunkCache.getChunk(ServerChunkCache.java:272) ~[?:?]
|
||||
at net.minecraft.world.level.Level.getChunk(Level.java:900) ~[?:?]
|
||||
at net.minecraft.world.level.Level.getBlockState(Level.java:1175) ~[?:?]
|
||||
at org.bukkit.craftbukkit.v1_20_R2.block.CraftBlock.getType(CraftBlock.java:238) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at fr.neatmonster.nocheatplus.compat.bukkit.BlockCacheBukkit.fetchTypeId(BlockCacheBukkit.java:52) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.utilities.map.BlockCache.getOrCreateNode(BlockCache.java:317) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.utilities.map.BlockCache.getType(BlockCache.java:379) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.utilities.map.BlockProperties.collectFlagsSimple(BlockProperties.java:4454) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.utilities.location.RichBoundsLocation.collectBlockFlags(RichBoundsLocation.java:1327) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.utilities.location.RichBoundsLocation.collectBlockFlags(RichBoundsLocation.java:1309) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.checks.moving.model.LocationData.setExtraProperties(LocationData.java:92) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.checks.moving.model.MoveData.setWithExtraProperties(MoveData.java:207) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.checks.moving.MovingData.resetPlayerPositions(MovingData.java:585) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.checks.moving.util.AuxMoving.resetPositionsAndMediumProperties(AuxMoving.java:116) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.checks.moving.MovingListener.onPlayerTeleportMonitor(MovingListener.java:1989) ~[NoCheatPlus.jar:?]
|
||||
at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
|
||||
at java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
|
||||
at fr.neatmonster.nocheatplus.event.mini.MultiListenerRegistry$AutoListener.onEvent(MultiListenerRegistry.java:82) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.event.mini.MiniListenerNode.onEvent(MiniListenerNode.java:157) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.event.mini.EventRegistryBukkit$4.execute(EventRegistryBukkit.java:124) ~[NoCheatPlus.jar:?]
|
||||
at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:77) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:git-SparklyPaper-"049a8e5"]
|
||||
at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||||
at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||||
at org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer.teleport(CraftPlayer.java:1354) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer.teleport(CraftPlayer.java:1252) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at net.perfectdreams.dreamcore.utils.extensions.EntityExtensionsKt.teleportToServerSpawn(EntityExtensions.kt:13) ~[DreamCore-reobf.jar:?]
|
||||
at net.perfectdreams.dreamcore.utils.extensions.EntityExtensionsKt.teleportToServerSpawnWithEffects(EntityExtensions.kt:20) ~[DreamCore-reobf.jar:?]
|
||||
at net.perfectdreams.dreamcore.utils.extensions.EntityExtensionsKt.teleportToServerSpawnWithEffects$default(EntityExtensions.kt:19) ~[DreamCore-reobf.jar:?]
|
||||
at net.perfectdreams.dreamxizum.utils.ArenaXizum.finishArena(ArenaXizum.kt:185) ~[DreamXizum-reobf.jar:?]
|
||||
at net.perfectdreams.dreamxizum.listeners.XizumListener.onDeath(XizumListener.kt:55) ~[DreamXizum-reobf.jar:?]
|
||||
at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor732.execute(Unknown Source) ~[?:?]
|
||||
at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||||
at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:77) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:git-SparklyPaper-"049a8e5"]
|
||||
at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||||
at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||||
at org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory.callPlayerDeathEvent(CraftEventFactory.java:984) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at net.minecraft.server.level.ServerPlayer.die(ServerPlayer.java:961) ~[?:?]
|
||||
at net.minecraft.world.entity.LivingEntity.hurt(LivingEntity.java:1548) ~[?:?]
|
||||
at net.minecraft.world.entity.player.Player.hurt(Player.java:973) ~[?:?]
|
||||
at net.minecraft.server.level.ServerPlayer.hurt(ServerPlayer.java:1130) ~[?:?]
|
||||
at net.minecraft.world.entity.projectile.AbstractArrow.onHitEntity(AbstractArrow.java:402) ~[?:?]
|
||||
at net.minecraft.world.entity.projectile.Projectile.onHit(Projectile.java:206) ~[?:?]
|
||||
at net.minecraft.world.entity.projectile.Projectile.preOnHit(Projectile.java:197) ~[?:?]
|
||||
at net.minecraft.world.entity.projectile.AbstractArrow.preOnHit(AbstractArrow.java:296) ~[?:?]
|
||||
at net.minecraft.world.entity.projectile.AbstractArrow.tick(AbstractArrow.java:232) ~[?:?]
|
||||
at net.minecraft.world.entity.projectile.Arrow.tick(Arrow.java:112) ~[?:?]
|
||||
at net.minecraft.server.level.ServerLevel.tickNonPassenger(ServerLevel.java:1392) ~[?:?]
|
||||
at net.minecraft.world.level.Level.guardEntityTick(Level.java:1314) ~[?:?]
|
||||
at net.minecraft.server.level.ServerLevel.lambda$tick$8(ServerLevel.java:906) ~[?:?]
|
||||
at net.minecraft.world.level.entity.EntityTickList.forEach(EntityTickList.java:49) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at net.minecraft.server.level.ServerLevel.tick(ServerLevel.java:886) ~[?:?]
|
||||
at net.minecraft.server.MinecraftServer.lambda$tickChildren$16(MinecraftServer.java:1600) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) ~[?:?]
|
||||
at java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[?:?]
|
||||
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
|
||||
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
|
||||
at java.lang.Thread.run(Thread.java:1583) ~[?:?]
|
||||
```
|
||||
|
||||
The Change Tracker also has a concurrency bug. NoCheatPlus uses `setAccess` to control which world is being used in `BlockCacheBukkit`. This is fine in a sequential ticking server, but in a parallel ticking server, there is a race condition where one server level tick thread changes the world to `World1`, then another thread changes the world to `World2`, then whoops, a crash happens!
|
||||
|
||||
This also breaks the movement checks above: While the movement checks do have `synchronized` in their methods, the falling block checks don't, and both use the same BlockCache instance (handled by `setAccess`).
|
||||
|
||||
https://github.com/Updated-NoCheatPlus/NoCheatPlus/blob/64bf374fd39297bab5b7adb646063debbd12643e/NCPCompatBukkit/src/main/java/fr/neatmonster/nocheatplus/compat/bukkit/BlockCacheBukkit.java#L52
|
||||
|
||||
To work around this, disable `changetracker.active` and `changetracker.pistons`.
|
||||
|
||||
I think that NoCheatPlus may have other concurrency issues too due to its use of `setAccess` around the code. In fact, I think that NoCheatPlus has the same issues in Folia too, even tho NoCheatPlus is marked as "Folia supported".
|
||||
|
||||
```javastacktrace
|
||||
[03:35:04] [serverlevel-tick-worker-6/ERROR]: THE SERVER IS GOING TO CRASH! - Thread serverlevel-tick-worker-6 failed main thread check: Cannot query another world's (world) chunk (-817, -45) in a ServerLevelTickThread - Is tick thread? true; Is server level tick thread? true; Currently ticking level: Resources; Is iterating over levels? true; Are we going to hard throw? false
|
||||
java.lang.Throwable: null
|
||||
at net.minecraft.server.level.ServerChunkCache.getChunk(ServerChunkCache.java:272) ~[?:?]
|
||||
at net.minecraft.world.level.Level.getChunk(Level.java:900) ~[?:?]
|
||||
at net.minecraft.world.level.Level.getBlockState(Level.java:1175) ~[?:?]
|
||||
at org.bukkit.craftbukkit.v1_20_R2.block.CraftBlock.getType(CraftBlock.java:238) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at fr.neatmonster.nocheatplus.compat.bukkit.BlockCacheBukkit.fetchTypeId(BlockCacheBukkit.java:52) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.utilities.map.BlockCache.getOrCreateNode(BlockCache.java:317) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.utilities.map.BlockCache.getOrCreateBlockCacheNode(BlockCache.java:442) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker.addBlock(BlockChangeTracker.java:571) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker.addBlocks(BlockChangeTracker.java:433) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker.addBlocks(BlockChangeTracker.java:394) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeListener.onEntityChangeBlock(BlockChangeListener.java:300) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeListener.access$200(BlockChangeListener.java:56) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeListener$2.onEvent(BlockChangeListener.java:99) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeListener$2.onEvent(BlockChangeListener.java:93) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.event.mini.MiniListenerNode.onEvent(MiniListenerNode.java:157) ~[NoCheatPlus.jar:?]
|
||||
at fr.neatmonster.nocheatplus.event.mini.EventRegistryBukkit$4.execute(EventRegistryBukkit.java:124) ~[NoCheatPlus.jar:?]
|
||||
at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:77) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:git-SparklyPaper-"049a8e5"]
|
||||
at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||||
at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615) ~[sparklypaper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||||
at org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory.callEntityChangeBlockEvent(CraftEventFactory.java:1411) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory.callEntityChangeBlockEvent(CraftEventFactory.java:1403) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at net.minecraft.world.entity.item.FallingBlockEntity.fall(FallingBlockEntity.java:98) ~[?:?]
|
||||
at net.minecraft.world.entity.item.FallingBlockEntity.fall(FallingBlockEntity.java:92) ~[?:?]
|
||||
at net.minecraft.world.level.block.FallingBlock.tick(FallingBlock.java:37) ~[?:?]
|
||||
at net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase.tick(BlockBehaviour.java:1205) ~[?:?]
|
||||
at net.minecraft.server.level.ServerLevel.tickBlock(ServerLevel.java:1340) ~[?:?]
|
||||
at net.minecraft.world.ticks.LevelTicks.runCollectedTicks(LevelTicks.java:197) ~[?:?]
|
||||
at net.minecraft.world.ticks.LevelTicks.tick(LevelTicks.java:94) ~[?:?]
|
||||
at net.minecraft.server.level.ServerLevel.tick(ServerLevel.java:848) ~[?:?]
|
||||
at net.minecraft.server.MinecraftServer.lambda$tickChildren$16(MinecraftServer.java:1600) ~[sparklypaper-1.20.2.jar:git-SparklyPaper-"049a8e5"]
|
||||
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) ~[?:?]
|
||||
at java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[?:?]
|
||||
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
|
||||
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
|
||||
at java.lang.Thread.run(Thread.java:1583) ~[?:?]
|
||||
```
|
||||
|
||||
## MyPet
|
||||
|
||||
If a player mounted on a Warden teleports to another world, the server crashes.
|
||||
|
||||
This is caused by https://github.com/MyPetORG/MyPet/issues/1647 and can even cause issues in vanilla Paper. In vanilla Paper, instead of crashing the server, the player is teleported back to the Warden's location.
|
||||
|
||||
Fork that removes the affecting code: https://github.com/SparklyPower/MyPet
|
||||
45
docs/PARALLEL_NOTES.md
Normal file
45
docs/PARALLEL_NOTES.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Parallel World Ticking Notes
|
||||
|
||||
Notes about the Parallel World Ticking implementation.
|
||||
|
||||
i'm stoopid so this may have a lot of dumb incorrect stuff pls don't hurt me spottedleaf :(
|
||||
|
||||
## Opening an inventory after a world switch
|
||||
|
||||
If you have an event that teleports the player, and somehow that event also opens an BlockEntity inventory, the server will lock up waiting for chunks on another world.
|
||||
|
||||
Example:
|
||||
```kotlin
|
||||
@EventHandler
|
||||
fun onInteract(e: PlayerInteractEvent) {
|
||||
e.player.teleport(Location(Bukkit.getWorld("..."), 0.0, 0.0, 0.0))
|
||||
}
|
||||
```
|
||||
|
||||
If you right-click on a chest, the player will be teleported, the chest will open... and the server will lock up!
|
||||
|
||||
This happens because the player is teleported BEFORE the inventory has been opened, the inventory is only opened AFTER the player has been teleported.
|
||||
|
||||
In a normal Paper server, this ain't a huge deal: The inventory is closed when the player is ticked in the new world, since it will fail the `stillValid` check.
|
||||
|
||||
In a parallel environment however, the `stillValid` and plugins listening for `InventoryCloseEvent` while loading the `holder` may load chunks from other worlds in a different ServerLevel Tick Thread! This locks up since we CANNOT load chunks from other worlds, because well, you know, it attempts to load on the main thread, but because the main thread is blocked...
|
||||
|
||||
To work around this issue...
|
||||
* `openMenu` will ignore any open menu requests until player `hasTickedAtLeastOnceInNewWorld`.
|
||||
* There are additional checks in the `BaseContainerBlockEntity#canUnlock`, to reject containers that were attempted to be opened after the player switched worlds.
|
||||
|
||||
In the future, it would be nice if `openMenu` could check if the container is still valid in the new world and then decide if the container should be closed. Currently, to open inventories after a teleport, wait 1 tick.
|
||||
|
||||
## TickThread Checks in NMS Level `setBlockEntity` and `getBlockEntity`
|
||||
|
||||
I attempted to add TickThread checks to these two methods, however, it seems like StarLight DOES access these block entities in a different "Tuinity Chunk System Worker" thread. Heck, even CoreProtect accesses these block entities from an async thread!
|
||||
|
||||
I looked up what Folia does, and it seems that they do have thread checks there, but it seems that they only check if it is a tick thread instead of checking if the current thread is related to the current world.
|
||||
|
||||
To be honest, it seems that `getBlockEntity` is thread safe. EXCEPT if you are accessing block entities from a separate `ServerLevelTickThread`! This will cause a main thread chunk load, and that will freeze the server.
|
||||
|
||||
The `capturedTileEntities` is also sus, but the map itself doesn't seem to be ever iterated, the only time it is iterated is via `entrySet()`. Maybe just to be extra sure, synchronize it? (Folia doesn't do that)
|
||||
|
||||
So, instead of doing what Folia does, we check `getBlockEntity` it via `ensureTickThreadOrAsyncThread`.
|
||||
|
||||
However, `setBlockEntity` still has the `ensureTickThread` check.
|
||||
103
docs/PARALLEL_WORLD_TICKING.md
Normal file
103
docs/PARALLEL_WORLD_TICKING.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Parallel World Ticking
|
||||
|
||||
"mom can we have folia?"
|
||||
|
||||
"we already have folia at home"
|
||||
|
||||
folia at home: *this*
|
||||
|
||||
## ⚠️⚠️⚠️ THIS IS EXPERIMENTAL ⚠️⚠️⚠️
|
||||
|
||||
DON'T COMPLAIN IF YOUR SERVER EXPLODES. AFTER ALL, SPARKLYPAPER WAS MADE FOR SPARKLYPOWER, ABSOUTELY NO SUPPORT FOR YOU!!!
|
||||
|
||||
In the Minecraft server world, there are various ways of implementing concurrent ticking, such as...
|
||||
|
||||
* The Vanilla Way™: All worlds are ticked sequentially.
|
||||
* Parallel World Ticking: All worlds are ticked in parallel, but each tick waits for all worlds to be processed before proceeding.
|
||||
* Asynchronous World Ticking: All worlds are ticked asynchronously, each world with its own tick rate.
|
||||
* Asynchronous Region Ticking: Chunks are split up in regions, and each region are ticked asynchronously. This is what [Folia](https://github.com/PaperMC/Folia) does.
|
||||
* Truly independent servers: Each server has its own world, so each world is completely isolated from each other.
|
||||
|
||||
SparklyPaper moves world ticking to its first logical step after The Vanilla Way™, moving Vanilla's world ticking into separate threads, allowing worlds to be ticked in parallel. Every tick waits until all worlds finishes ticking. This means that your TPS is _mostly_ based off the MSPT of your heaviest world. Useful to spread out players to multiple worlds! (Example: Multiple Survival worlds)
|
||||
|
||||
We do run Parallel World Ticking in production @ [SparklyPower](https://sparklypower.net/). Our server was being bottlenecked by all the things our Survival world needed to tick (such as Villagers, blocks like farmland, those pesky Axolotls, etc) that we needed a solution. We first tried to go with the "one server per world" but after looking at so much complexity we would need to handle, such as...
|
||||
|
||||
* How are you going to do Inventory syncing?
|
||||
* How are you going to maintain multiple servers?
|
||||
* How are you going to query how many players are connected to *all* servers?
|
||||
* How are you going to implement player name autocomplete, if players may be on different servers?
|
||||
* Are you ready to reimplement vanilla commands such as `/tp` and `/give`?
|
||||
|
||||
That we decided that maybe it was time to pull off a crazy patch to do parallel world ticking instead.
|
||||
|
||||
Synchronization issues *are expected to happen*. Thread checks are still present and only the `ServerLevelTickThread` that is bound to the modified world, or the main `TickThread`, can modify world data. Plugins will break with Parallel World Ticking if they attempt to modify other worlds in a thread that isn't theirs! Plugins can work around this by scheduling a main thread task.
|
||||
|
||||
## Well, if Folia has a superior ticking system, why not use Folia?
|
||||
|
||||
Folia is amazing, in fact, a lot of the code used for parallel world ticking was heavily inspired by... and, uh, copied from... Folia.
|
||||
|
||||
However, due to the way Folia works, a lot of plugins *will* break and require updates from their maintainers to make them work in Folia. Which is why Folia, by default, does not allow plugins not marked as Folia compatible to work.
|
||||
|
||||
With Parallel World Ticking, not a lot of plugins *should* break since plugins mostly do stuff on the same world that the event has been triggered, and player actions and a bunch of other stuff are still processed on the main thread, so a lot of plugins should, hopefully, work out of the box. Of course, the downside is that Parallel World Ticking does not provide all the performance advantages that Folia has, and you are forced to break down your players into multiple worlds to get those sweet TPS improvements, while with Folia you only need to get players to be far from each other in the same world.
|
||||
|
||||
However, there are things that WILL BREAK, such as teleporting players/entities to another world on events called on the server level tick thread. You can work around these issues by scheduling these API calls to be run after all worlds are ticked.
|
||||
|
||||
Because Minecraft's vanilla mechanics do not interfer with other worlds, aside from portal/end portal respawn, maintaining the vanilla behavior for items is easier compared to Folia.
|
||||
|
||||
So this is a stopgap solution while Folia isn't ready for prime time yet, without requiring you to do the whole "one servers for each world" approach, which is way harder to develop, handle, and maintain.
|
||||
|
||||
Besides, it is fun!
|
||||
|
||||
## If this is possible, why Paper doesn't have it built-in?
|
||||
|
||||
Plugins, CraftBukkit, and the Minecraft Server itself, weren't really made with parallel world ticking in mind.
|
||||
|
||||
Adding this to Paper would ensure that a lot of angry users would complain to Paper that plugin xyz isn't working. This is also the reason about why Folia only allows loading plugins marked as Folia compatible.
|
||||
|
||||
## I've heard that anything async related in the Minecraft code is bad
|
||||
|
||||
Yes, attempts to do ✨ async magic ✨ in the Minecraft server aren't a new thing. This has been done in the past in Akarin, Yatopia, and other forks ([patch](https://github.com/YatopiaMC/Yatopia/blob/1a54ef2f995f049d4fcf1f2bd084691126f10046/patches/server/0046-Option-for-async-world-ticking.patch)).
|
||||
|
||||
However, the previous attempts were based on "`synchronize` and pray", which is why they were unstable and not recommended for production.
|
||||
|
||||
MrPowerGamerBR from 2018 had even made a meme making fun of these patches.
|
||||
|
||||

|
||||
|
||||
SparklyPaper follows Folia's footsteps and keeps everything in check, keeping all tick thread checks in the code. Most of the groundwork had already been done by Spottedleaf and the Paper team. thx leaf *pets the leaf*
|
||||
|
||||
But of course, that doesn't mean that SparklyPaper is perfect! If your server crashes, that ain't gonna be my fault xd.
|
||||
|
||||
## I live on the edge and I don't want random "not on main thread" throws
|
||||
|
||||
Off-main thread throws can be disabled with `-Dsparklypaper.disableHardThrow=true`, but THIS IS NOT RECOMMENDED because you SHOULD FIX THE ISSUES THEMSELVES instead of RISKING DATA CORRUPTION!!! The server will still log the stacktrace of where the exception happened.
|
||||
|
||||
In fact, disabling throws is not an easy way out: Yes, you avoid some functions borking out. But, if the tick thread check has failed, your server is probably going to crash anyway. Example: If a plugin is attempting to teleport a player to world X while they are in a TickThread of world Y, the server will lock up because loading chunks outside of the world's tick thread or from an async thread is not allowed. In fact, if you had kept hard throws enabled, your server wouldn't have crashed because the request would've been denied! Fix the dang issues instead!!!
|
||||
|
||||
## Profiling with Spark
|
||||
|
||||
By default, Spark will profile the `Server thread` thread, which ain't good for us if we want to profile what is being used in our worlds.
|
||||
|
||||
Spark has an undocumented configuration setting to configure what threads the background profiler will track.
|
||||
|
||||
In Spark's `config.json`, add `"backgroundProfilerThreadDumper": "all"` to dump all threads used in server.
|
||||
|
||||
When looking at the profiler result, the server level tick threads are named `serverlevel-tick-worker [WorldNameHere]`.
|
||||
|
||||
We use a single thread per world instead of a thread pool to be easier to track down what thing is lagging which world. However, parallel thread execution is limited by a semaphore based on the `parallel-world-ticking.threads` value.
|
||||
|
||||
## Can I disable this?
|
||||
|
||||
SparklyPaper was tailor-made for SparklyPower with features that we need, so... no.
|
||||
|
||||
I'm not even sure why this question is even here considering that the only real SparklyPaper user is myself. :3
|
||||
|
||||
*Theorically* if you really want to, you can set `parallel-world-ticking.threads` to 1, and it would work just like the good old days. None of the additional checks are removed, however.
|
||||
|
||||
## Plugin Incompatibilities
|
||||
|
||||
[Here's a list of plugins that have issues with parallel world ticking](PARALLEL_INCOMPATIBLE_PLUGINS.md)
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
If you are curious about things that I've learned while making this, I wrote [some notes about why some things were implemented the way that they are](PARALLEL_NOTES.md).
|
||||
BIN
docs/per-world-mspt.png
Normal file
BIN
docs/per-world-mspt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 386 KiB |
Reference in New Issue
Block a user