From d17fb532f1c53a00367eede072341fd1816bb051 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sun, 16 Feb 2025 12:17:53 -0800 Subject: [PATCH] Move file patches to feature patches This is to assist in updating upstream, as the current file patch system is brittle and fails to handle conflicts well. --- .../features/0001-Region-Threading-Base.patch | 19827 ++++++++++++++++ ...ns.patch => 0002-Max-pending-logins.patch} | 2 +- ...k-system-throughput-counters-to-tps.patch} | 2 +- ...ates-in-non-loaded-or-non-owned-chu.patch} | 2 +- ...world-tile-entities-on-worldgen-thr.patch} | 0 ...tion-to-player-position-on-player-d.patch} | 2 +- ...filer.patch => 0007-Region-profiler.patch} | 6 +- ...d.patch => 0008-Add-watchdog-thread.patch} | 2 +- .../common/misc/NearbyPlayers.java.patch | 23 - .../moonrise/paper/PaperHooks.java.patch | 20 - .../util/BaseChunkSystemHooks.java.patch | 87 - .../level/chunk/ChunkData.java.patch | 11 - .../level/entity/EntityLookup.java.patch | 32 - .../server/ServerEntityLookup.java.patch | 36 - .../RegionizedPlayerChunkLoader.java.patch | 20 - .../queue/ChunkUnloadQueue.java.patch | 42 - .../scheduling/ChunkHolderManager.java.patch | 391 - .../scheduling/ChunkTaskScheduler.java.patch | 75 - .../scheduling/NewChunkHolder.java.patch | 33 - .../collisions/CollisionUtil.java.patch | 20 - .../activation/ActivationRange.java.patch | 174 - .../redstone/RedstoneWireTurbo.java.patch | 19 - .../RegionShutdownThread.java.patch | 229 - .../threadedregions/RegionizedData.java.patch | 238 - .../RegionizedServer.java.patch | 458 - .../RegionizedTaskQueue.java.patch | 810 - .../RegionizedWorldData.java.patch | 773 - .../paper/threadedregions/Schedule.java.patch | 94 - .../threadedregions/TeleportUtils.java.patch | 85 - .../ThreadedRegionizer.java.patch | 1408 -- .../paper/threadedregions/TickData.java.patch | 336 - .../TickRegionScheduler.java.patch | 567 - .../threadedregions/TickRegions.java.patch | 416 - .../commands/CommandServerHealth.java.patch | 358 - .../commands/CommandUtil.java.patch | 124 - .../scheduler/FoliaRegionScheduler.java.patch | 427 - .../SimpleThreadLocalRandomSource.java.patch | 82 - .../util/ThreadLocalRandomSource.java.patch | 76 - .../commands/CommandSourceStack.java.patch | 11 - .../minecraft/commands/Commands.java.patch | 112 - .../BoatDispenseItemBehavior.java.patch | 11 - .../DefaultDispenseItemBehavior.java.patch | 11 - .../dispenser/DispenseItemBehavior.java.patch | 149 - .../EquipmentDispenseItemBehavior.java.patch | 11 - .../MinecartDispenseItemBehavior.java.patch | 11 - .../ProjectileDispenseBehavior.java.patch | 11 - .../ShearsDispenseItemBehavior.java.patch | 11 - .../ShulkerBoxDispenseBehavior.java.patch | 11 - .../framework/GameTestHelper.java.patch | 11 - .../framework/GameTestServer.java.patch | 17 - .../minecraft/network/Connection.java.patch | 336 - .../network/protocol/PacketUtils.java.patch | 37 - .../server/MinecraftServer.java.patch | 779 - .../commands/AdvancementCommands.java.patch | 32 - .../commands/AttributeCommand.java.patch | 188 - .../ClearInventoryCommands.java.patch | 20 - .../server/commands/DamageCommand.java.patch | 35 - .../DefaultGameModeCommands.java.patch | 18 - .../server/commands/EffectCommands.java.patch | 44 - .../server/commands/EnchantCommand.java.patch | 100 - .../commands/ExperienceCommand.java.patch | 39 - .../commands/FillBiomeCommand.java.patch | 49 - .../server/commands/FillCommand.java.patch | 49 - .../commands/ForceLoadCommand.java.patch | 93 - .../commands/GameModeCommand.java.patch | 24 - .../server/commands/GiveCommand.java.patch | 47 - .../server/commands/KillCommand.java.patch | 13 - .../server/commands/PlaceCommand.java.patch | 134 - .../server/commands/RecipeCommand.java.patch | 30 - .../commands/SetBlockCommand.java.patch | 42 - .../commands/SetSpawnCommand.java.patch | 15 - .../server/commands/SummonCommand.java.patch | 26 - .../commands/TeleportCommand.java.patch | 47 - .../server/commands/TimeCommand.java.patch | 33 - .../server/commands/WeatherCommand.java.patch | 29 - .../commands/WorldBorderCommand.java.patch | 169 - .../dedicated/DedicatedServer.java.patch | 39 - .../server/level/ChunkMap.java.patch | 227 - .../server/level/DistanceManager.java.patch | 53 - .../server/level/ServerChunkCache.java.patch | 276 - .../level/ServerEntityGetter.java.patch | 32 - .../server/level/ServerLevel.java.patch | 1133 - .../server/level/ServerPlayer.java.patch | 631 - .../level/ServerPlayerGameMode.java.patch | 40 - .../server/level/TicketType.java.patch | 22 - .../server/level/WorldGenRegion.java.patch | 24 - .../ServerCommonPacketListenerImpl.java.patch | 89 - ...ConfigurationPacketListenerImpl.java.patch | 70 - .../ServerConnectionListener.java.patch | 28 - .../ServerGamePacketListenerImpl.java.patch | 396 - .../ServerLoginPacketListenerImpl.java.patch | 33 - .../server/players/BanListEntry.java.patch | 50 - .../players/OldUsersConverter.java.patch | 11 - .../server/players/PlayerList.java.patch | 419 - .../server/players/StoredUserList.java.patch | 29 - .../net/minecraft/util/SpawnUtil.java.patch | 11 - .../world/RandomSequences.java.patch | 83 - .../damagesource/CombatTracker.java.patch | 20 - .../damagesource/DamageSource.java.patch | 17 - .../damagesource/FallLocation.java.patch | 11 - .../minecraft/world/entity/Entity.java.patch | 1088 - .../world/entity/LivingEntity.java.patch | 153 - .../net/minecraft/world/entity/Mob.java.patch | 61 - .../world/entity/PortalProcessor.java.patch | 15 - .../world/entity/TamableAnimal.java.patch | 38 - .../world/entity/ai/Brain.java.patch | 21 - .../behavior/GoToPotentialJobSite.java.patch | 17 - .../ai/behavior/PoiCompetitorScan.java.patch | 14 - .../ai/behavior/YieldJobSite.java.patch | 17 - .../entity/ai/goal/FollowOwnerGoal.java.patch | 11 - .../GroundPathNavigation.java.patch | 14 - .../ai/navigation/PathNavigation.java.patch | 34 - .../entity/ai/sensing/PlayerSensor.java.patch | 11 - .../ai/sensing/TemptingSensor.java.patch | 11 - .../entity/ai/village/VillageSiege.java.patch | 134 - .../ai/village/poi/PoiManager.java.patch | 39 - .../world/entity/animal/Bee.java.patch | 26 - .../world/entity/animal/Cat.java.patch | 11 - .../boss/enderdragon/EndCrystal.java.patch | 11 - .../entity/decoration/ItemFrame.java.patch | 12 - .../entity/item/FallingBlockEntity.java.patch | 11 - .../world/entity/item/ItemEntity.java.patch | 27 - .../world/entity/item/PrimedTnt.java.patch | 22 - .../world/entity/monster/Vex.java.patch | 11 - .../entity/monster/ZombieVillager.java.patch | 20 - .../entity/npc/AbstractVillager.java.patch | 22 - .../world/entity/npc/CatSpawner.java.patch | 26 - .../world/entity/npc/Villager.java.patch | 28 - .../npc/WanderingTraderSpawner.java.patch | 90 - .../world/entity/player/Player.java.patch | 17 - .../projectile/AbstractArrow.java.patch | 14 - .../AbstractHurtingProjectile.java.patch | 14 - .../FireworkRocketEntity.java.patch | 14 - .../entity/projectile/FishingHook.java.patch | 50 - .../entity/projectile/LlamaSpit.java.patch | 14 - .../entity/projectile/Projectile.java.patch | 87 - .../projectile/SmallFireball.java.patch | 11 - .../projectile/ThrowableProjectile.java.patch | 14 - .../projectile/ThrownEnderpearl.java.patch | 140 - .../world/entity/raid/Raid.java.patch | 61 - .../world/entity/raid/Raider.java.patch | 11 - .../world/entity/raid/Raids.java.patch | 119 - .../vehicle/MinecartCommandBlock.java.patch | 14 - .../entity/vehicle/MinecartHopper.java.patch | 11 - .../minecraft/world/item/ItemStack.java.patch | 128 - .../minecraft/world/item/MapItem.java.patch | 61 - .../minecraft/world/item/SignItem.java.patch | 20 - .../component/LodestoneTracker.java.patch | 14 - .../world/level/BaseCommandBlock.java.patch | 35 - .../world/level/EntityGetter.java.patch | 61 - .../minecraft/world/level/Level.java.patch | 404 - .../world/level/LevelAccessor.java.patch | 29 - .../world/level/LevelReader.java.patch | 28 - .../world/level/NaturalSpawner.java.patch | 11 - .../world/level/ServerExplosion.java.patch | 24 - .../level/ServerLevelAccessor.java.patch | 15 - .../world/level/StructureManager.java.patch | 48 - .../world/level/block/BedBlock.java.patch | 11 - .../world/level/block/Block.java.patch | 13 - .../world/level/block/BushBlock.java.patch | 11 - .../block/DaylightDetectorBlock.java.patch | 11 - .../level/block/DispenserBlock.java.patch | 20 - .../level/block/DoublePlantBlock.java.patch | 11 - .../level/block/EndGatewayBlock.java.patch | 50 - .../level/block/EndPortalBlock.java.patch | 32 - .../world/level/block/FarmBlock.java.patch | 13 - .../world/level/block/FungusBlock.java.patch | 14 - .../world/level/block/HoneyBlock.java.patch | 11 - .../level/block/LightningRodBlock.java.patch | 11 - .../level/block/MushroomBlock.java.patch | 11 - .../level/block/NetherPortalBlock.java.patch | 62 - .../world/level/block/Portal.java.patch | 13 - .../level/block/RedStoneWireBlock.java.patch | 80 - .../level/block/RedstoneTorchBlock.java.patch | 53 - .../world/level/block/SaplingBlock.java.patch | 39 - .../block/SpreadingSnowyDirtBlock.java.patch | 11 - .../level/block/WitherSkullBlock.java.patch | 11 - .../block/entity/BeaconBlockEntity.java.patch | 20 - .../level/block/entity/BlockEntity.java.patch | 33 - .../entity/CommandBlockEntity.java.patch | 16 - .../entity/ConduitBlockEntity.java.patch | 20 - .../block/entity/HopperBlockEntity.java.patch | 150 - .../SculkCatalystBlockEntity.java.patch | 14 - .../TheEndGatewayBlockEntity.java.patch | 246 - .../entity/TickingBlockEntity.java.patch | 9 - .../level/block/grower/TreeGrower.java.patch | 84 - .../block/piston/PistonBaseBlock.java.patch | 11 - .../piston/PistonMovingBlockEntity.java.patch | 43 - .../world/level/border/WorldBorder.java.patch | 31 - .../level/chunk/ChunkGenerator.java.patch | 11 - .../world/level/chunk/LevelChunk.java.patch | 117 - .../storage/SerializableChunkData.java.patch | 11 - .../dimension/end/EndDragonFight.java.patch | 65 - .../level/levelgen/PatrolSpawner.java.patch | 54 - .../level/levelgen/PhantomSpawner.java.patch | 38 - .../feature/EndPlatformFeature.java.patch | 11 - .../structure/StructureStart.java.patch | 63 - .../CollectingNeighborUpdater.java.patch | 10 - .../level/saveddata/SavedData.java.patch | 23 - .../level/saveddata/maps/MapIndex.java.patch | 24 - .../maps/MapItemSavedData.java.patch | 172 - .../storage/DimensionDataStorage.java.patch | 42 - .../world/ticks/LevelChunkTicks.java.patch | 24 - .../world/ticks/LevelTicks.java.patch | 102 - .../features/0001-Region-Threading-Base.patch | 5332 +++++ ...date-Logo.patch => 0002-Update-Logo.patch} | 0 ...changes.patch => 0003-Build-changes.patch} | 0 ... => 0004-Fix-tests-by-removing-them.patch} | 0 ...filer.patch => 0005-Region-profiler.patch} | 0 ...d.patch => 0006-Add-watchdog-thread.patch} | 0 ...n.patch => 0007-Add-TPS-From-Region.patch} | 0 .../common/util/TickThread.java.patch | 212 - .../io/papermc/paper/SparksFly.java.patch | 57 - .../paper/adventure/ChatProcessor.java.patch | 20 - .../ClickCallbackProviderImpl.java.patch | 57 - .../paper/command/PaperCommands.java.patch | 11 - .../subcommands/EntityCommand.java.patch | 11 - .../subcommands/HeapDumpCommand.java.patch | 12 - .../subcommands/ReloadCommand.java.patch | 12 - .../GlobalConfiguration.java.patch | 20 - .../WorldConfiguration.java.patch | 17 - .../entity/PaperSchoolableFish.java.patch | 19 - .../activation/ActivationType.java.patch | 11 - .../manager/PaperPermissionManager.java.patch | 177 - .../PaperPluginInstanceManager.java.patch | 16 - .../configuration/PaperPluginMeta.java.patch | 24 - .../PaperPluginProviderFactory.java.patch | 14 - .../SpigotPluginProviderFactory.java.patch | 19 - .../EntityScheduler.java.patch | 17 - .../io/papermc/paper/util/MCUtil.java.patch | 41 - .../bukkit/craftbukkit/CraftServer.java.patch | 133 - .../bukkit/craftbukkit/CraftWorld.java.patch | 432 - .../craftbukkit/block/CraftBlock.java.patch | 201 - .../block/CraftBlockEntityState.java.patch | 22 - .../block/CraftBlockState.java.patch | 25 - .../block/CraftBlockStates.java.patch | 22 - .../ConsoleCommandCompleter.java.patch | 20 - .../entity/AbstractProjectile.java.patch | 31 - .../entity/CraftAbstractArrow.java.patch | 10 - .../entity/CraftAbstractHorse.java.patch | 19 - .../entity/CraftAbstractSkeleton.java.patch | 20 - .../entity/CraftAbstractVillager.java.patch | 19 - .../entity/CraftAbstractWindCharge.java.patch | 10 - .../entity/CraftAgeable.java.patch | 19 - .../craftbukkit/entity/CraftAllay.java.patch | 19 - .../entity/CraftAmbient.java.patch | 19 - .../entity/CraftAnimals.java.patch | 19 - .../entity/CraftAreaEffectCloud.java.patch | 19 - .../entity/CraftArmadillo.java.patch | 10 - .../entity/CraftArmorStand.java.patch | 19 - .../craftbukkit/entity/CraftArrow.java.patch | 24 - .../entity/CraftAxolotl.java.patch | 19 - .../craftbukkit/entity/CraftBat.java.patch | 19 - .../craftbukkit/entity/CraftBee.java.patch | 19 - .../craftbukkit/entity/CraftBlaze.java.patch | 19 - .../CraftBlockAttachedEntity.java.patch | 19 - .../entity/CraftBlockDisplay.java.patch | 19 - .../craftbukkit/entity/CraftBoat.java.patch | 19 - .../craftbukkit/entity/CraftBogged.java.patch | 10 - .../craftbukkit/entity/CraftBreeze.java.patch | 10 - .../entity/CraftBreezeWindCharge.java.patch | 10 - .../craftbukkit/entity/CraftCamel.java.patch | 19 - .../craftbukkit/entity/CraftCat.java.patch | 19 - .../entity/CraftCaveSpider.java.patch | 19 - .../entity/CraftChestBoat.java.patch | 19 - .../entity/CraftChestedHorse.java.patch | 19 - .../entity/CraftChicken.java.patch | 19 - .../craftbukkit/entity/CraftCod.java.patch | 19 - .../entity/CraftComplexPart.java.patch | 19 - .../craftbukkit/entity/CraftCow.java.patch | 19 - .../entity/CraftCreaking.java.patch | 19 - .../entity/CraftCreature.java.patch | 19 - .../entity/CraftCreeper.java.patch | 24 - .../entity/CraftDisplay.java.patch | 19 - .../entity/CraftDolphin.java.patch | 19 - .../entity/CraftDrowned.java.patch | 19 - .../craftbukkit/entity/CraftEgg.java.patch | 19 - .../entity/CraftEnderCrystal.java.patch | 19 - .../entity/CraftEnderDragon.java.patch | 19 - .../entity/CraftEnderDragonPart.java.patch | 19 - .../entity/CraftEnderPearl.java.patch | 19 - .../entity/CraftEnderSignal.java.patch | 19 - .../entity/CraftEnderman.java.patch | 19 - .../entity/CraftEndermite.java.patch | 19 - .../craftbukkit/entity/CraftEntity.java.patch | 134 - .../craftbukkit/entity/CraftEvoker.java.patch | 19 - .../entity/CraftEvokerFangs.java.patch | 19 - .../entity/CraftExperienceOrb.java.patch | 19 - .../entity/CraftFallingBlock.java.patch | 19 - .../entity/CraftFireball.java.patch | 19 - .../entity/CraftFirework.java.patch | 19 - .../craftbukkit/entity/CraftFish.java.patch | 19 - .../entity/CraftFishHook.java.patch | 19 - .../craftbukkit/entity/CraftFlying.java.patch | 19 - .../craftbukkit/entity/CraftFox.java.patch | 19 - .../craftbukkit/entity/CraftFrog.java.patch | 19 - .../craftbukkit/entity/CraftGhast.java.patch | 19 - .../craftbukkit/entity/CraftGiant.java.patch | 19 - .../entity/CraftGlowItemFrame.java.patch | 19 - .../entity/CraftGlowSquid.java.patch | 19 - .../craftbukkit/entity/CraftGoat.java.patch | 19 - .../craftbukkit/entity/CraftGolem.java.patch | 19 - .../entity/CraftGuardian.java.patch | 19 - .../entity/CraftHanging.java.patch | 19 - .../craftbukkit/entity/CraftHoglin.java.patch | 19 - .../craftbukkit/entity/CraftHorse.java.patch | 19 - .../entity/CraftHumanEntity.java.patch | 19 - .../entity/CraftIllager.java.patch | 19 - .../entity/CraftIllusioner.java.patch | 19 - .../entity/CraftInteraction.java.patch | 19 - .../entity/CraftIronGolem.java.patch | 19 - .../craftbukkit/entity/CraftItem.java.patch | 19 - .../entity/CraftItemDisplay.java.patch | 19 - .../entity/CraftItemFrame.java.patch | 19 - .../entity/CraftLargeFireball.java.patch | 19 - .../craftbukkit/entity/CraftLeash.java.patch | 24 - .../entity/CraftLightningStrike.java.patch | 19 - .../entity/CraftLivingEntity.java.patch | 24 - .../craftbukkit/entity/CraftLlama.java.patch | 19 - .../entity/CraftLlamaSpit.java.patch | 19 - .../entity/CraftMagmaCube.java.patch | 19 - .../craftbukkit/entity/CraftMarker.java.patch | 19 - .../entity/CraftMinecart.java.patch | 19 - .../entity/CraftMinecartCommand.java.patch | 19 - .../entity/CraftMinecartContainer.java.patch | 19 - .../entity/CraftMinecartFurnace.java.patch | 19 - .../entity/CraftMinecartHopper.java.patch | 20 - .../entity/CraftMinecartMobSpawner.java.patch | 19 - .../entity/CraftMinecartTNT.java.patch | 19 - .../craftbukkit/entity/CraftMob.java.patch | 28 - .../entity/CraftMonster.java.patch | 19 - .../entity/CraftMushroomCow.java.patch | 24 - .../craftbukkit/entity/CraftOcelot.java.patch | 19 - .../entity/CraftOminousItemSpawner.java.patch | 10 - .../entity/CraftPainting.java.patch | 19 - .../craftbukkit/entity/CraftPanda.java.patch | 19 - .../craftbukkit/entity/CraftParrot.java.patch | 19 - .../entity/CraftPhantom.java.patch | 19 - .../craftbukkit/entity/CraftPig.java.patch | 19 - .../entity/CraftPigZombie.java.patch | 19 - .../craftbukkit/entity/CraftPiglin.java.patch | 19 - .../entity/CraftPiglinAbstract.java.patch | 19 - .../entity/CraftPiglinBrute.java.patch | 19 - .../entity/CraftPillager.java.patch | 19 - .../craftbukkit/entity/CraftPlayer.java.patch | 77 - .../entity/CraftPolarBear.java.patch | 20 - .../entity/CraftProjectile.java.patch | 19 - .../entity/CraftPufferFish.java.patch | 19 - .../craftbukkit/entity/CraftRabbit.java.patch | 19 - .../craftbukkit/entity/CraftRaider.java.patch | 19 - .../entity/CraftRavager.java.patch | 19 - .../craftbukkit/entity/CraftSalmon.java.patch | 19 - .../craftbukkit/entity/CraftSheep.java.patch | 19 - .../entity/CraftShulker.java.patch | 19 - .../entity/CraftShulkerBullet.java.patch | 19 - .../entity/CraftSilverfish.java.patch | 19 - .../entity/CraftSizedFireball.java.patch | 19 - .../entity/CraftSkeleton.java.patch | 19 - .../entity/CraftSkeletonHorse.java.patch | 19 - .../craftbukkit/entity/CraftSlime.java.patch | 19 - .../entity/CraftSmallFireball.java.patch | 19 - .../entity/CraftSniffer.java.patch | 19 - .../entity/CraftSnowball.java.patch | 19 - .../entity/CraftSnowman.java.patch | 19 - .../entity/CraftSpectralArrow.java.patch | 19 - .../entity/CraftSpellcaster.java.patch | 19 - .../craftbukkit/entity/CraftSpider.java.patch | 19 - .../craftbukkit/entity/CraftSquid.java.patch | 19 - .../entity/CraftStrider.java.patch | 19 - .../entity/CraftTNTPrimed.java.patch | 19 - .../entity/CraftTadpole.java.patch | 19 - .../entity/CraftTameableAnimal.java.patch | 19 - .../entity/CraftTextDisplay.java.patch | 19 - .../CraftThrowableProjectile.java.patch | 19 - .../entity/CraftThrownExpBottle.java.patch | 19 - .../entity/CraftThrownPotion.java.patch | 20 - .../entity/CraftTraderLlama.java.patch | 19 - .../entity/CraftTrident.java.patch | 19 - .../entity/CraftTropicalFish.java.patch | 19 - .../craftbukkit/entity/CraftTurtle.java.patch | 19 - .../craftbukkit/entity/CraftVex.java.patch | 19 - .../entity/CraftVillager.java.patch | 19 - .../entity/CraftVillagerZombie.java.patch | 19 - .../entity/CraftVindicator.java.patch | 19 - .../entity/CraftWanderingTrader.java.patch | 19 - .../craftbukkit/entity/CraftWarden.java.patch | 19 - .../entity/CraftWaterMob.java.patch | 19 - .../entity/CraftWindCharge.java.patch | 10 - .../craftbukkit/entity/CraftWitch.java.patch | 19 - .../craftbukkit/entity/CraftWither.java.patch | 19 - .../entity/CraftWitherSkull.java.patch | 19 - .../craftbukkit/entity/CraftWolf.java.patch | 19 - .../craftbukkit/entity/CraftZoglin.java.patch | 19 - .../craftbukkit/entity/CraftZombie.java.patch | 19 - .../event/CraftEventFactory.java.patch | 29 - .../scheduler/CraftScheduler.java.patch | 10 - .../scoreboard/CraftScoreboard.java.patch | 26 - .../CraftScoreboardManager.java.patch | 18 - .../util/CraftMagicNumbers.java.patch | 15 - .../util/DelegatedGeneratorAccess.java.patch | 21 - .../org/spigotmc/SpigotCommand.java.patch | 18 - .../java/org/spigotmc/SpigotConfig.java.patch | 20 - .../org/spigotmc/SpigotWorldConfig.java.patch | 11 - .../paper/plugin/TestPluginMeta.java.patch | 16 - 404 files changed, 25167 insertions(+), 24357 deletions(-) create mode 100644 folia-server/minecraft-patches/features/0001-Region-Threading-Base.patch rename folia-server/minecraft-patches/features/{0001-Max-pending-logins.patch => 0002-Max-pending-logins.patch} (96%) rename folia-server/minecraft-patches/features/{0002-Add-chunk-system-throughput-counters-to-tps.patch => 0003-Add-chunk-system-throughput-counters-to-tps.patch} (98%) rename folia-server/minecraft-patches/features/{0003-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch => 0004-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch} (98%) rename folia-server/minecraft-patches/features/{0004-Block-reading-in-world-tile-entities-on-worldgen-thr.patch => 0005-Block-reading-in-world-tile-entities-on-worldgen-thr.patch} (100%) rename folia-server/minecraft-patches/features/{0005-Sync-vehicle-position-to-player-position-on-player-d.patch => 0006-Sync-vehicle-position-to-player-position-on-player-d.patch} (95%) rename folia-server/minecraft-patches/features/{0006-Region-profiler.patch => 0007-Region-profiler.patch} (99%) rename folia-server/minecraft-patches/features/{0007-Add-watchdog-thread.patch => 0008-Add-watchdog-thread.patch} (98%) delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java.patch delete mode 100644 folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/entity/activation/ActivationRange.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/redstone/RedstoneWireTurbo.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionShutdownThread.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedData.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedServer.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedTaskQueue.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedWorldData.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/Schedule.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TeleportUtils.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/ThreadedRegionizer.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickData.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickRegionScheduler.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickRegions.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/commands/CommandServerHealth.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/commands/CommandUtil.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java.patch delete mode 100644 folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/commands/CommandSourceStack.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/commands/Commands.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/gametest/framework/GameTestHelper.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/network/Connection.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/AdvancementCommands.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/AttributeCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/ClearInventoryCommands.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/DamageCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/EffectCommands.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/EnchantCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/ExperienceCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/FillBiomeCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/ForceLoadCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/GiveCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/KillCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/RecipeCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/SummonCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/TimeCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/DistanceManager.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerEntityGetter.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/TicketType.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/players/BanListEntry.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/server/players/StoredUserList.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/util/SpawnUtil.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/RandomSequences.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/CombatTracker.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/FallLocation.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/Mob.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/PortalProcessor.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/Brain.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/YieldJobSite.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/PlayerSensor.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/village/poi/PoiManager.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/animal/Bee.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/animal/Cat.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/monster/Vex.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/Villager.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raid.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raider.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raids.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/MinecartHopper.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/item/MapItem.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/item/SignItem.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/EntityGetter.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/LevelAccessor.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/LevelReader.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/StructureManager.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/BedBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/Block.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/BushBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/Portal.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/RedStoneWireBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/TickingBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/SavedData.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/maps/MapIndex.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/ticks/LevelChunkTicks.java.patch delete mode 100644 folia-server/minecraft-patches/sources/net/minecraft/world/ticks/LevelTicks.java.patch create mode 100644 folia-server/paper-patches/features/0001-Region-Threading-Base.patch rename folia-server/paper-patches/features/{0001-Update-Logo.patch => 0002-Update-Logo.patch} (100%) rename folia-server/paper-patches/features/{0002-Build-changes.patch => 0003-Build-changes.patch} (100%) rename folia-server/paper-patches/features/{0003-Fix-tests-by-removing-them.patch => 0004-Fix-tests-by-removing-them.patch} (100%) rename folia-server/paper-patches/features/{0004-Region-profiler.patch => 0005-Region-profiler.patch} (100%) rename folia-server/paper-patches/features/{0005-Add-watchdog-thread.patch => 0006-Add-watchdog-thread.patch} (100%) rename folia-server/paper-patches/features/{0006-Add-TPS-From-Region.patch => 0007-Add-TPS-From-Region.patch} (100%) delete mode 100644 folia-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/SparksFly.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/adventure/ChatProcessor.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/PaperCommands.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/entity/activation/ActivationType.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/io/papermc/paper/util/MCUtil.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotCommand.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotConfig.java.patch delete mode 100644 folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotWorldConfig.java.patch delete mode 100644 folia-server/paper-patches/files/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java.patch diff --git a/folia-server/minecraft-patches/features/0001-Region-Threading-Base.patch b/folia-server/minecraft-patches/features/0001-Region-Threading-Base.patch new file mode 100644 index 0000000..57325ab --- /dev/null +++ b/folia-server/minecraft-patches/features/0001-Region-Threading-Base.patch @@ -0,0 +1,19827 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Apr 1997 05:37:42 -0800 +Subject: [PATCH] Region Threading Base + + +diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..0027a3896c0cfce2f46eca8a0a77a90223723dc7 100644 +--- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java ++++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +@@ -244,7 +244,7 @@ public final class NearbyPlayers { + created.addPlayer(parameter, type); + type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); + +- ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; ++ //((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; // Folia - region threading + } + } + +@@ -263,10 +263,7 @@ public final class NearbyPlayers { + + if (chunk.isEmpty()) { + NearbyPlayers.this.byChunk.remove(chunkKey); +- final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey); +- if (chunkData != null) { +- chunkData.nearbyPlayers = null; +- } ++ // Folia - region threading + } + } + } +diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java +index 4d344559a20a0c35c181e297e81788c747363ec9..779e6b7d025da185b33a963e42e91a56908e46dc 100644 +--- a/ca/spottedleaf/moonrise/paper/PaperHooks.java ++++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java +@@ -105,7 +105,7 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo + } + + 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 +127,7 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo + 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; +@@ -275,4 +275,4 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo + public int modifyEntityTrackingRange(final Entity entity, final int currentRange) { + return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange); + } +-} +\ No newline at end of file ++} +diff --git a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java +index ece1261b67033e946dfc20a96872708755bffe0a..8d67b4629c69d3039b199aaad45533d1acde114e 100644 +--- a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java ++++ b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java +@@ -80,18 +80,23 @@ public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.co + + @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 +107,13 @@ public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.co + + @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 +125,7 @@ public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.co + + @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 +136,18 @@ public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.co + + @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 +@@ -191,4 +184,4 @@ public abstract class BaseChunkSystemHooks implements ca.spottedleaf.moonrise.co + public void updateMaps(final ServerLevel world, final ServerPlayer player) { + ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getPlayerChunkLoader().updatePlayer(player); + } +-} +\ No newline at end of file ++} +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java +index 8b9dc582627b46843f4b5ea6f8c3df2d8cac46fa..306216138e21c41937e4728e8004220a02d6ea4b 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java +@@ -5,7 +5,7 @@ import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; + public final class ChunkData { + + private int referenceCount = 0; +- public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players ++ //public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players // Folia - region threading + + public ChunkData() { + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +index 7554c109c35397bc1a43dd80e87764fd78645bbf..db16fe8d664f9b04710200d63439564cb97c0066 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +@@ -460,6 +460,19 @@ public abstract class EntityLookup implements LevelEntityGetter { + 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 +999,9 @@ public abstract class EntityLookup implements LevelEntityGetter { + 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 + } + } + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java +index 26207443b1223119c03db478d7e816d9cdf8e618..a89ee24c6aed46af23c5de7ae2234c7902f1c0f4 100644 +--- 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 +17,7 @@ public final class ServerEntityLookup extends EntityLookup { + private static final Entity[] EMPTY_ENTITY_ARRAY = new Entity[0]; + + private final ServerLevel serverWorld; +- public final ReferenceList trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker ++ // Folia - move to regionized world data + + public ServerEntityLookup(final ServerLevel world, final LevelCallback worldCallback) { + super(world, worldCallback); +@@ -75,6 +75,7 @@ public final class ServerEntityLookup extends EntityLookup { + if (entity instanceof ServerPlayer player) { + ((ChunkSystemServerLevel)this.serverWorld).moonrise$getNearbyPlayers().addPlayer(player); + } ++ this.world.getCurrentWorldData().addEntity(entity); // Folia - region threading + } + + @Override +@@ -87,14 +88,14 @@ public final class ServerEntityLookup extends EntityLookup { + @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 + } + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +index dd2509996bfd08e8c3f9f2be042229eac6d7692d..f77dcf5a42ff34a1624ddf16bcce2abee81194bb 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +@@ -216,7 +216,7 @@ public final class RegionizedPlayerChunkLoader { + 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 +304,7 @@ public final class RegionizedPlayerChunkLoader { + 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 +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java +index 7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309..4bfcae47ed76346e6200514ebce5b04f907c5026 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java +@@ -29,6 +29,39 @@ public final class ChunkUnloadQueue { + + public static record SectionToUnload(int sectionX, int sectionZ, long order, int count) {} + ++ // Folia start - threaded regions ++ public List retrieveForCurrentRegion() { ++ final io.papermc.paper.threadedregions.ThreadedRegionizer.ThreadedRegion region = ++ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion(); ++ final io.papermc.paper.threadedregions.ThreadedRegionizer regionizer = region.regioniser; ++ final int shift = this.coordinateShift; ++ ++ final List ret = new ArrayList<>(); ++ ++ for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) { ++ final ConcurrentLong2ReferenceChainedHashTable.TableEntry 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 retrieveForAllRegions() { + final List ret = new ArrayList<>(); + +@@ -141,4 +174,4 @@ public final class ChunkUnloadQueue { + this.order = order; + } + } +-} +\ No newline at end of file ++} +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +index b5817aa8f537593f6d9fc6b612c82ccccb250ac7..aae97116a22a87cffd4756d566da3acd96ce2ae0 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +@@ -56,6 +56,14 @@ import java.util.concurrent.atomic.AtomicReference; + 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 +86,83 @@ public final class ChunkHolderManager { + private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); + private final ServerLevel world; + private final ChunkTaskScheduler taskScheduler; +- private long currentTick; ++ // Folia start - region threading ++ public static final class HolderManagerRegionData { ++ private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); ++ private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { ++ if (c1 == c2) { ++ return 0; ++ } + +- private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); +- private final ObjectRBTreeSet 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); ++ } + } + +- final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); ++ public void split(final int chunkToRegionShift, final Long2ReferenceOpenHashMap regionToData, ++ final ReferenceOpenHashSet 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 ++ } + +- if (saveTickCompare != 0) { +- return saveTickCompare; ++ 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 ++ } + } ++ } + +- final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); +- final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); ++ private ChunkHolderManager.HolderManagerRegionData getCurrentRegionData() { ++ final ThreadedRegionizer.ThreadedRegion region = ++ TickRegionScheduler.getCurrentRegion(); + +- if (coord1 == coord2) { +- throw new IllegalStateException("Duplicate chunkholder in auto save queue"); ++ if (region == null) { ++ return null; + } + +- return Long.compare(coord1, coord2); +- }); ++ 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 +247,13 @@ public final class ChunkHolderManager { + } + + 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 +263,10 @@ public final class ChunkHolderManager { + } + + 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 +288,35 @@ public final class ChunkHolderManager { + } + + 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 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 +330,38 @@ public final class ChunkHolderManager { + + 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 holders = this.getChunkHolders(); ++ // 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 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 (logProgress) { ++ if (first && logProgress) { // Folia - region threading + LOGGER.info("Saving all chunkholders for world '" + WorldUtil.getWorldName(this.world) + "'"); + } + +@@ -292,6 +390,12 @@ public final class ChunkHolderManager { + } + 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 +431,7 @@ public final class ChunkHolderManager { + } + } + } +- if (flush) { ++ if (last && flush) { // Folia - region threading + MoonriseRegionFileIO.flush(this.world); + try { + MoonriseRegionFileIO.flushRegionStorages(this.world); +@@ -732,7 +836,13 @@ public final class ChunkHolderManager { + } + + public void tick() { +- ++this.currentTick; ++ // Folia start - region threading ++ final ThreadedRegionizer.ThreadedRegion 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 +856,7 @@ public final class ChunkHolderManager { + 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 +1141,56 @@ public final class ChunkHolderManager { + if (changedFullStatus.isEmpty()) { + return; + } +- if (!TickThread.isTickThread()) { +- this.taskScheduler.scheduleChunkTask(() -> { +- final ArrayDeque 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 pendingFullLoadUpdate = this.pendingFullLoadUpdate; +- for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { +- pendingFullLoadUpdate.add(changedFullStatus.get(i)); ++ // Folia start - region threading ++ final Long2ObjectOpenHashMap> sectionToUpdates = new Long2ObjectOpenHashMap<>(); ++ final List thisRegionHolders = new ArrayList<>(); ++ ++ final int regionShift = this.world.moonrise$getRegionChunkShift(); ++ final ThreadedRegionizer.ThreadedRegion 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>> iterator = sectionToUpdates.long2ObjectEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2ObjectMap.Entry> entry = iterator.next(); ++ final long sectionKey = entry.getLongKey(); ++ ++ final int chunkX = CoordinateUtils.getChunkX(sectionKey) << regionShift; ++ final int chunkZ = CoordinateUtils.getChunkZ(sectionKey) << regionShift; ++ ++ final List 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 +1203,7 @@ public final class ChunkHolderManager { + throw new IllegalStateException("Cannot unload chunks recursively"); + } + final int sectionShift = this.unloadQueue.coordinateShift; // sectionShift <= lock shift +- final List unloadSectionsForRegion = this.unloadQueue.retrieveForAllRegions(); ++ final List unloadSectionsForRegion = this.unloadQueue.retrieveForCurrentRegion(); // Folia - threaded regions + int unloadCountTentative = 0; + for (final ChunkUnloadQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { + final ChunkUnloadQueue.UnloadSection section +@@ -1381,7 +1521,13 @@ public final class ChunkHolderManager { + + // only call on tick thread + private boolean processPendingFullUpdate() { +- final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; ++ // Folia start - region threading ++ final HolderManagerRegionData data = this.getCurrentRegionData(); ++ if (data == null) { ++ return false; ++ } ++ final ArrayDeque pendingFullLoadUpdate = data.pendingFullLoadUpdate; ++ // Folia end - region threading + + boolean ret = false; + +@@ -1392,9 +1538,7 @@ public final class ChunkHolderManager { + 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(); + } + } +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java +index 67532b85073b7978254a0b04caadfe822679e61f..cba2d16c0cb5adc92952990ef95b1c979eafd40f 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java +@@ -122,7 +122,7 @@ public final class ChunkTaskScheduler { + 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 +337,13 @@ public final class ChunkTaskScheduler { + }; + + // 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 +828,7 @@ public final class ChunkTaskScheduler { + */ + @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 +837,7 @@ public final class ChunkTaskScheduler { + + 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,9 +846,27 @@ public final class ChunkTaskScheduler { + + 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(); + this.parallelGenExecutor.halt(); +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java +index e4a5fa25ed368fc4662c30934da2963ef446d782..601ed36413bbbf9c17e530b42906986e441237fd 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java +@@ -1359,10 +1359,10 @@ public final class NewChunkHolder { + 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 +1383,7 @@ public final class NewChunkHolder { + } + + // 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 consumer : consumers) { + try { + consumer.accept(chunk); +@@ -1411,7 +1411,7 @@ public final class NewChunkHolder { + } + + // 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 consumer : consumers) { + try { + consumer.accept(chunk); +diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java +index e04bd54744335fb5398c6e4f7ce8b981f35bfb7d..471b6d49d77e03665ffc269d17ab46f225e3ce1c 100644 +--- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java ++++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java +@@ -1940,6 +1940,17 @@ public final class CollisionUtil { + + for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { + for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ // Folia start - region threading ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(world, currChunkX, currChunkZ, 4)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ intoAABB.add(getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ continue; ++ } ++ } ++ // Folia end - region threading + final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks); + + if (chunk == null) { +diff --git a/io/papermc/paper/entity/activation/ActivationRange.java b/io/papermc/paper/entity/activation/ActivationRange.java +index ade6110cc6adb1263c0359ff7e96e96b959e61f3..c260741a87513b89a5cc62c543fb9f990f86491e 100644 +--- a/io/papermc/paper/entity/activation/ActivationRange.java ++++ b/io/papermc/paper/entity/activation/ActivationRange.java +@@ -48,33 +48,34 @@ public final class ActivationRange { + + 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 +123,11 @@ public final class ActivationRange { + 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 +137,37 @@ public final class ActivationRange { + 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 entities = world.getEntities((Entity) null, ActivationRange.maxBB, e -> true); ++ final java.util.List 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 +177,14 @@ public final class ActivationRange { + * + * @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 +198,7 @@ public final class ActivationRange { + */ + 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 +206,10 @@ public final class ActivationRange { + 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 +306,16 @@ public final class ActivationRange { + 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; + } +diff --git a/io/papermc/paper/redstone/RedstoneWireTurbo.java b/io/papermc/paper/redstone/RedstoneWireTurbo.java +index ff747a1ecdf3c888bca0d69de4f85dcd810b6139..5a76c93eada8db35b1ddbb562ccfbd2f0d35f0ca 100644 +--- a/io/papermc/paper/redstone/RedstoneWireTurbo.java ++++ b/io/papermc/paper/redstone/RedstoneWireTurbo.java +@@ -829,14 +829,14 @@ public final class RedstoneWireTurbo { + 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 +diff --git a/io/papermc/paper/threadedregions/RegionShutdownThread.java b/io/papermc/paper/threadedregions/RegionShutdownThread.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bbd14cf34438a9366f5ff29f1acba4282d77d983 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/RegionShutdownThread.java +@@ -0,0 +1,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 shuttingDown; ++ ++ public RegionShutdownThread(final String name) { ++ super(name); ++ this.setUncaughtExceptionHandler((thread, thr) -> { ++ LOGGER.error("Error shutting down server", thr); ++ }); ++ } ++ ++ static ThreadedRegionizer.ThreadedRegion 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 region, ++ final ServerLevel world) { ++ try { ++ this.shuttingDown = region; ++ final List 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 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 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> ++ 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> ++ 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() ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/RegionizedData.java b/io/papermc/paper/threadedregions/RegionizedData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a1043c426d031755b57b77a9b2eec685e9861b13 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/RegionizedData.java +@@ -0,0 +1,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. ++ *

++ * Note: 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. ++ *

++ *

++ * Note: 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. ++ *

++ *

++ * Regionised data may be world-checked. 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: ++ *

++ *         {@code
++ *         public class EntityTickList {
++ *             private final List 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 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 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 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 =
++ *                  new RegionizedData<>(null, () -> new TickTimes(), ...);
++ *         }
++ *         }
++ *     
++ * In general, it is advised that if a RegionizedData object is instantiated per world, that world checking ++ * is enabled for it by passing the world to the constructor. ++ *

++ */ ++public final class RegionizedData { ++ ++ private final ServerLevel world; ++ private final Supplier initialValueSupplier; ++ private final RegioniserCallback callback; ++ ++ /** ++ * Creates a regionised data holder. The provided initial value supplier may not be null, and it must ++ * never produce {@code null} values. ++ *

++ * 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. ++ *

++ *

++ * 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 and region, ++ * however using {@code null} for tasks to run at the end of a tick is valid since the tasks are tied to ++ * region only. ++ *

++ * @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 supplier, final RegioniserCallback 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 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 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. ++ *

++ * It is critical to note that each function is called while holding the region lock. ++ *

++ */ ++ public static interface RegioniserCallback { ++ ++ /** ++ * Completely merges the data in {@code from} to {@code into}. ++ *

++ * Calculating Tick Offsets: ++ * 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}. ++ *

++ *

++ * Critical Notes: ++ *

  • ++ *
      ++ * 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. ++ *
    ++ *
      ++ * This function may not throw any exceptions, or the server will be left in an unrecoverable state. ++ *
    ++ *
  • ++ *

    ++ * ++ * @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}. ++ *

    ++ * 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: ++ *

    ++         *         {@code
    ++         *         int chunkX = ...;
    ++         *         int chunkZ = ...;
    ++         *
    ++         *         int regionSectionX = chunkX >> chunkToRegionShift;
    ++         *         int regionSectionZ = chunkZ >> chunkToRegionShift;
    ++         *         long regionSectionKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(regionSectionX, regionSectionZ);
    ++         *         }
    ++         *     
    ++ *

    ++ *

    ++ * 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. ++ *

    ++ *

    ++ * 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. ++ *

    ++ * ++ * @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 regionToData, final ReferenceOpenHashSet dataSet ++ ); ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/RegionizedServer.java b/io/papermc/paper/threadedregions/RegionizedServer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1382c695c4991488b113401e231875ddc74f6b01 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/RegionizedServer.java +@@ -0,0 +1,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 worlds = new CopyOnWriteArrayList<>(); ++ private final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); ++ ++ private final MultiThreadedQueue 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 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> 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 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 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 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 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) { ++ ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..745ab870310733b569681f5280895bb9798620a4 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java +@@ -0,0 +1,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 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 globalChunkTask = new MultiThreadedQueue<>(); ++ private final ConcurrentLong2ReferenceChainedHashTable 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 regioniser = this.world.regioniser; ++ final ThreadedRegionizer.ThreadedRegion 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 regioniser, ++ final Long2ReferenceOpenHashMap> 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[] 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 queue : this.queues) { ++ ret += queue.size(); ++ } ++ ++ return ret; ++ } ++ } ++ ++ public boolean isEmpty() { ++ final ArrayDeque[] 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[] thisQueues) { ++ synchronized (target) { ++ final ArrayDeque[] otherQueues = target.queues; ++ for (int i = 0; i < thisQueues.length; ++i) { ++ final ArrayDeque fromQ = thisQueues[i]; ++ final ArrayDeque 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 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 regioniser, ++ final Long2ReferenceOpenHashMap> into) { ++ final Reference2ReferenceOpenHashMap, ArrayDeque[]> ++ 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[] thisQueues = this.queues; ++ for (int i = 0; i < thisQueues.length; ++i) { ++ final ArrayDeque 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 ++ region = into.get(sectionKey); ++ if (region == null) { ++ throw new IllegalStateException(); ++ } ++ ++ split.computeIfAbsent(region, (keyInMap) -> { ++ final ArrayDeque[] 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, ArrayDeque[]>> ++ iterator = split.reference2ReferenceEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Reference2ReferenceMap.Entry, ArrayDeque[]> ++ 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[] 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[] 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 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); ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/RegionizedWorldData.java b/io/papermc/paper/threadedregions/RegionizedWorldData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c6e487a4c14e6b82533881d01f32349b9ae28728 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java +@@ -0,0 +1,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 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 iterator = from.entityTickList.unsafeIterator(); iterator.hasNext();) { ++ into.entityTickList.add(iterator.next()); ++ } ++ for (final Iterator iterator = from.navigatingMobs.unsafeIterator(); iterator.hasNext();) { ++ into.navigatingMobs.add(iterator.next()); ++ } ++ for (final Iterator iterator = from.trackerEntities.iterator(); iterator.hasNext();) { ++ into.trackerEntities.add(iterator.next()); ++ } ++ for (final Iterator 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 iterator = from.entityTickingChunks.iterator(); iterator.hasNext();) { ++ into.entityTickingChunks.add(iterator.next()); ++ } ++ for (final Iterator iterator = from.tickingChunks.iterator(); iterator.hasNext();) { ++ into.tickingChunks.add(iterator.next()); ++ } ++ for (final Iterator 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 regionToData, ++ final ReferenceOpenHashSet 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> levelTicksBlockRegionData = new Long2ReferenceOpenHashMap<>(regionToData.size(), 0.75f); ++ final Long2ReferenceOpenHashMap> levelTicksFluidRegionData = new Long2ReferenceOpenHashMap<>(regionToData.size(), 0.75f); ++ ++ for (final Iterator> iterator = regionToData.long2ReferenceEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2ReferenceMap.Entry 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 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 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 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 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 localPlayers = new ArrayList<>(); ++ private final NearbyPlayers nearbyPlayers; ++ private final ReferenceList allEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); ++ private final ReferenceList loadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); ++ private final IteratorSafeOrderedReferenceSet entityTickList = new IteratorSafeOrderedReferenceSet<>(); ++ private final IteratorSafeOrderedReferenceSet navigatingMobs = new IteratorSafeOrderedReferenceSet<>(); ++ public final ReferenceList trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker ++ public final ReferenceList trackerUnloadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker ++ ++ // block ticking ++ private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); ++ private final LevelTicks blockLevelTicks; ++ private final LevelTicks fluidLevelTicks; ++ ++ // tile entity ticking ++ private final List pendingBlockEntityTickers = new ArrayList<>(); ++ private final List 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 entityTickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDER_ARRAY); ++ private final ReferenceList tickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDER_ARRAY); ++ private final ReferenceList 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 capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper ++ public final Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper ++ public List 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 explosionDensityCache = new HashMap<>(64, 0.25f); ++ public final PathTypeCache pathTypesByPosCache = new PathTypeCache(); ++ public final List temporaryChunkTickList = new java.util.ArrayList<>(); ++ public final Set chunkHoldersToBroadcast = new ReferenceLinkedOpenHashSet<>(); ++ ++ // not transient ++ public java.util.ArrayDeque redstoneUpdateInfos; ++ ++ // Mob spawning ++ public final ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap 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 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 getLocalEntities() { ++ return this.allEntities; ++ } ++ ++ public Entity[] getLocalEntitiesCopy() { ++ return Arrays.copyOf(this.allEntities.getRawData(), this.allEntities.size(), Entity[].class); ++ } ++ ++ public List 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 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 action) { ++ final IteratorSafeOrderedReferenceSet.Iterator 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 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 blockEvents) { ++ for (final BlockEventData blockEventData : blockEvents) { ++ this.pushBlockEvent(blockEventData); ++ } ++ } ++ ++ public void removeIfBlockEvents(final Predicate predicate) { ++ for (final Iterator 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 getBlockLevelTicks() { ++ return this.blockLevelTicks; ++ } ++ ++ public LevelTicks 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 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 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 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 getChunks() { ++ return this.chunks; ++ } ++ ++ public int getEntityTickingChunkCount() { ++ return this.entityTickingChunks.size(); ++ } ++ ++ public int getChunkCount() { ++ return this.chunks.size(); ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/Schedule.java b/io/papermc/paper/threadedregions/Schedule.java +new file mode 100644 +index 0000000000000000000000000000000000000000..820b1c4dc1b19ee8602333295f2034362f885a37 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/Schedule.java +@@ -0,0 +1,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; ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/TeleportUtils.java b/io/papermc/paper/threadedregions/TeleportUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2a64a5b2cf049661fe3f5a22ddfa39979624f5ec +--- /dev/null ++++ b/io/papermc/paper/threadedregions/TeleportUtils.java +@@ -0,0 +1,82 @@ ++package io.papermc.paper.threadedregions; ++ ++import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.event.player.PlayerTeleportEvent; ++import java.util.function.Consumer; ++ ++public final class TeleportUtils { ++ ++ public static void teleport(final T from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch, ++ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer onComplete) { ++ teleport(from, useFromRootVehicle, to, yaw, pitch, teleportFlags, cause, onComplete, null); ++ } ++ ++ public static void teleport(final T from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch, ++ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer onComplete, ++ final java.util.function.Predicate preTeleport) { ++ // retrieve coordinates ++ final CallbackCompletable positionCompletable = new CallbackCompletable<>(); ++ ++ positionCompletable.addWaiter( ++ (final Location loc, final Throwable thr) -> { ++ if (loc == null) { ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } ++ return; ++ } ++ final boolean scheduled = from.getBukkitEntity().taskScheduler.schedule( ++ (final T realFrom) -> { ++ final Vec3 pos = new Vec3( ++ loc.getX(), loc.getY(), loc.getZ() ++ ); ++ if (preTeleport != null && !preTeleport.test(realFrom)) { ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } ++ return; ++ } ++ (useFromRootVehicle ? realFrom.getRootVehicle() : realFrom).teleportAsync( ++ ((CraftWorld)loc.getWorld()).getHandle(), pos, null, null, null, ++ cause, teleportFlags, onComplete ++ ); ++ }, ++ (final Entity retired) -> { ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } ++ }, ++ 1L ++ ); ++ if (!scheduled) { ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } ++ } ++ } ++ ); ++ ++ final boolean scheduled = to.getBukkitEntity().taskScheduler.schedule( ++ (final Entity target) -> { ++ positionCompletable.complete(target.getBukkitEntity().getLocation()); ++ }, ++ (final Entity retired) -> { ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } ++ }, ++ 1L ++ ); ++ if (!scheduled) { ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } ++ } ++ } ++ ++ private TeleportUtils() {} ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/ThreadedRegionizer.java b/io/papermc/paper/threadedregions/ThreadedRegionizer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..604385af903845d966382ad0a4168798e4ed4a0e +--- /dev/null ++++ b/io/papermc/paper/threadedregions/ThreadedRegionizer.java +@@ -0,0 +1,1405 @@ ++package io.papermc.paper.threadedregions; ++ ++import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable; ++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; ++import ca.spottedleaf.moonrise.common.util.CoordinateUtils; ++import com.destroystokyo.paper.util.SneakyThrow; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongComparator; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkPos; ++import org.slf4j.Logger; ++import java.lang.invoke.VarHandle; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Set; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.concurrent.locks.StampedLock; ++import java.util.function.BooleanSupplier; ++import java.util.function.Consumer; ++ ++public final class ThreadedRegionizer, S extends ThreadedRegionizer.ThreadedRegionSectionData> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public final int regionSectionChunkSize; ++ public final int sectionChunkShift; ++ public final int minSectionRecalcCount; ++ public final int emptySectionCreateRadius; ++ public final int regionSectionMergeRadius; ++ public final double maxDeadRegionPercent; ++ public final ServerLevel world; ++ ++ private final SWMRLong2ObjectHashTable> sections = new SWMRLong2ObjectHashTable<>(); ++ private final SWMRLong2ObjectHashTable> regionsById = new SWMRLong2ObjectHashTable<>(); ++ private final RegionCallbacks callbacks; ++ private final StampedLock regionLock = new StampedLock(); ++ private Thread writeLockOwner; ++ ++ /* ++ static final record Operation(String type, int chunkX, int chunkZ) {} ++ private final MultiThreadedQueue ops = new MultiThreadedQueue<>(); ++ */ ++ ++ /* ++ * See REGION_LOGIC.md for complete details on what this class is doing ++ */ ++ ++ public ThreadedRegionizer(final int minSectionRecalcCount, final double maxDeadRegionPercent, ++ final int emptySectionCreateRadius, final int regionSectionMergeRadius, ++ final int regionSectionChunkShift, final ServerLevel world, ++ final RegionCallbacks callbacks) { ++ if (emptySectionCreateRadius <= 0) { ++ throw new IllegalStateException("Region section create radius must be > 0"); ++ } ++ if (regionSectionMergeRadius <= 0) { ++ throw new IllegalStateException("Region section merge radius must be > 0"); ++ } ++ this.regionSectionChunkSize = 1 << regionSectionChunkShift; ++ this.sectionChunkShift = regionSectionChunkShift; ++ this.minSectionRecalcCount = Math.max(2, minSectionRecalcCount); ++ this.maxDeadRegionPercent = maxDeadRegionPercent; ++ this.emptySectionCreateRadius = emptySectionCreateRadius; ++ this.regionSectionMergeRadius = regionSectionMergeRadius; ++ this.world = world; ++ this.callbacks = callbacks; ++ //this.loadTestData(); ++ } ++ ++ /* ++ private static String substr(String val, String prefix, int from) { ++ int idx = val.indexOf(prefix, from) + prefix.length(); ++ int idx2 = val.indexOf(',', idx); ++ if (idx2 == -1) { ++ idx2 = val.indexOf(']', idx); ++ } ++ return val.substring(idx, idx2); ++ } ++ ++ private void loadTestData() { ++ if (true) { ++ return; ++ } ++ try { ++ final JsonArray arr = JsonParser.parseReader(new FileReader("test.json")).getAsJsonArray(); ++ ++ List ops = new ArrayList<>(); ++ ++ for (JsonElement elem : arr) { ++ JsonObject obj = elem.getAsJsonObject(); ++ String val = obj.get("value").getAsString(); ++ ++ String type = substr(val, "type=", 0); ++ String x = substr(val, "chunkX=", 0); ++ String z = substr(val, "chunkZ=", 0); ++ ++ ops.add(new Operation(type, Integer.parseInt(x), Integer.parseInt(z))); ++ } ++ ++ for (Operation op : ops) { ++ switch (op.type) { ++ case "add": { ++ this.addChunk(op.chunkX, op.chunkZ); ++ break; ++ } ++ case "remove": { ++ this.removeChunk(op.chunkX, op.chunkZ); ++ break; ++ } ++ case "mark_ticking": { ++ this.sections.get(CoordinateUtils.getChunkKey(op.chunkX, op.chunkZ)).region.tryMarkTicking(); ++ break; ++ } ++ case "rel_region": { ++ if (this.sections.get(CoordinateUtils.getChunkKey(op.chunkX, op.chunkZ)).region.state == ThreadedRegion.STATE_TICKING) { ++ this.sections.get(CoordinateUtils.getChunkKey(op.chunkX, op.chunkZ)).region.markNotTicking(); ++ } ++ break; ++ } ++ } ++ } ++ ++ } catch (final Exception ex) { ++ throw new IllegalStateException(ex); ++ } ++ } ++ */ ++ ++ public void acquireReadLock() { ++ this.regionLock.readLock(); ++ } ++ ++ public void releaseReadLock() { ++ this.regionLock.tryUnlockRead(); ++ } ++ ++ private void acquireWriteLock() { ++ final Thread currentThread = Thread.currentThread(); ++ if (this.writeLockOwner == currentThread) { ++ throw new IllegalStateException("Cannot recursively operate in the regioniser"); ++ } ++ this.regionLock.writeLock(); ++ this.writeLockOwner = currentThread; ++ } ++ ++ private void releaseWriteLock() { ++ this.writeLockOwner = null; ++ this.regionLock.tryUnlockWrite(); ++ } ++ ++ private void onRegionCreate(final ThreadedRegion region) { ++ final ThreadedRegion conflict; ++ if ((conflict = this.regionsById.putIfAbsent(region.id, region)) != null) { ++ throw new IllegalStateException("Region " + region + " is already mapped to " + conflict); ++ } ++ } ++ ++ private void onRegionDestroy(final ThreadedRegion region) { ++ final ThreadedRegion removed = this.regionsById.remove(region.id); ++ if (removed != region) { ++ throw new IllegalStateException("Expected to remove " + region + ", but removed " + removed); ++ } ++ } ++ ++ public int getSectionCoordinate(final int chunkCoordinate) { ++ return chunkCoordinate >> this.sectionChunkShift; ++ } ++ ++ public long getSectionKey(final BlockPos pos) { ++ return CoordinateUtils.getChunkKey((pos.getX() >> 4) >> this.sectionChunkShift, (pos.getZ() >> 4) >> this.sectionChunkShift); ++ } ++ ++ public long getSectionKey(final ChunkPos pos) { ++ return CoordinateUtils.getChunkKey(pos.x >> this.sectionChunkShift, pos.z >> this.sectionChunkShift); ++ } ++ ++ public long getSectionKey(final Entity entity) { ++ final ChunkPos pos = entity.chunkPosition(); ++ return CoordinateUtils.getChunkKey(pos.x >> this.sectionChunkShift, pos.z >> this.sectionChunkShift); ++ } ++ ++ public void computeForAllRegions(final Consumer> consumer) { ++ this.regionLock.readLock(); ++ try { ++ this.regionsById.forEachValue(consumer); ++ } finally { ++ this.regionLock.tryUnlockRead(); ++ } ++ } ++ ++ public void computeForAllRegionsUnsynchronised(final Consumer> consumer) { ++ this.regionsById.forEachValue(consumer); ++ } ++ ++ public int computeForRegions(final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ, ++ final Consumer>> consumer) { ++ final int shift = this.sectionChunkShift; ++ final int fromSectionX = fromChunkX >> shift; ++ final int fromSectionZ = fromChunkZ >> shift; ++ final int toSectionX = toChunkX >> shift; ++ final int toSectionZ = toChunkZ >> shift; ++ this.acquireWriteLock(); ++ try { ++ final ReferenceOpenHashSet> set = new ReferenceOpenHashSet<>(); ++ ++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { ++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { ++ final ThreadedRegionSection section = this.sections.get(CoordinateUtils.getChunkKey(currX, currZ)); ++ if (section != null) { ++ set.add(section.getRegionPlain()); ++ } ++ } ++ } ++ ++ consumer.accept(set); ++ ++ return set.size(); ++ } finally { ++ this.releaseWriteLock(); ++ } ++ } ++ ++ public ThreadedRegion getRegionAtUnsynchronised(final int chunkX, final int chunkZ) { ++ final int sectionX = chunkX >> this.sectionChunkShift; ++ final int sectionZ = chunkZ >> this.sectionChunkShift; ++ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); ++ ++ final ThreadedRegionSection section = this.sections.get(sectionKey); ++ ++ return section == null ? null : section.getRegion(); ++ } ++ ++ public ThreadedRegion getRegionAtSynchronised(final int chunkX, final int chunkZ) { ++ final int sectionX = chunkX >> this.sectionChunkShift; ++ final int sectionZ = chunkZ >> this.sectionChunkShift; ++ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); ++ ++ // try an optimistic read ++ { ++ final long readAttempt = this.regionLock.tryOptimisticRead(); ++ final ThreadedRegionSection optimisticSection = this.sections.get(sectionKey); ++ final ThreadedRegion optimisticRet = ++ optimisticSection == null ? null : optimisticSection.getRegionPlain(); ++ if (this.regionLock.validate(readAttempt)) { ++ return optimisticRet; ++ } ++ } ++ ++ // failed, fall back to acquiring the lock ++ this.regionLock.readLock(); ++ try { ++ final ThreadedRegionSection section = this.sections.get(sectionKey); ++ ++ return section == null ? null : section.getRegionPlain(); ++ } finally { ++ this.regionLock.tryUnlockRead(); ++ } ++ } ++ ++ /** ++ * Adds a chunk to the regioniser. Note that it is illegal to add a chunk unless ++ * addChunk has not been called for it or removeChunk has been previously called. ++ * ++ *

    ++ * Note that it is illegal to additionally call addChunk or removeChunk for the same ++ * region section in parallel. ++ *

    ++ */ ++ public void addChunk(final int chunkX, final int chunkZ) { ++ final int sectionX = chunkX >> this.sectionChunkShift; ++ final int sectionZ = chunkZ >> this.sectionChunkShift; ++ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); ++ ++ // Given that for each section, no addChunk/removeChunk can occur in parallel, ++ // we can avoid the lock IF the section exists AND it has a non-zero chunk count. ++ { ++ final ThreadedRegionSection existing = this.sections.get(sectionKey); ++ if (existing != null && !existing.isEmpty()) { ++ existing.addChunk(chunkX, chunkZ); ++ return; ++ } // else: just acquire the write lock ++ } ++ ++ this.acquireWriteLock(); ++ try { ++ ThreadedRegionSection section = this.sections.get(sectionKey); ++ ++ List> newSections = new ArrayList<>(); ++ ++ if (section == null) { ++ // no section at all ++ section = new ThreadedRegionSection<>(sectionX, sectionZ, this, chunkX, chunkZ); ++ this.sections.put(sectionKey, section); ++ newSections.add(section); ++ } else { ++ section.addChunk(chunkX, chunkZ); ++ } ++ // due to the fast check from above, we know the section is empty whether we needed to create it or not ++ ++ // enforce the adjacency invariant by creating / updating neighbour sections ++ final int createRadius = this.emptySectionCreateRadius; ++ final int searchRadius = createRadius + this.regionSectionMergeRadius; ++ ReferenceOpenHashSet> nearbyRegions = null; ++ for (int dx = -searchRadius; dx <= searchRadius; ++dx) { ++ for (int dz = -searchRadius; dz <= searchRadius; ++dz) { ++ if ((dx | dz) == 0) { ++ continue; ++ } ++ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); ++ final boolean inCreateRange = squareDistance <= createRadius; ++ ++ final int neighbourX = dx + sectionX; ++ final int neighbourZ = dz + sectionZ; ++ final long neighbourKey = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); ++ ++ ThreadedRegionSection neighbourSection = this.sections.get(neighbourKey); ++ ++ if (neighbourSection != null) { ++ if (nearbyRegions == null) { ++ nearbyRegions = new ReferenceOpenHashSet<>(((searchRadius * 2 + 1) * (searchRadius * 2 + 1)) >> 1); ++ } ++ nearbyRegions.add(neighbourSection.getRegionPlain()); ++ } ++ ++ if (!inCreateRange) { ++ continue; ++ } ++ ++ // we need to ensure the section exists ++ if (neighbourSection != null) { ++ // nothing else to do ++ neighbourSection.incrementNonEmptyNeighbours(); ++ continue; ++ } ++ neighbourSection = new ThreadedRegionSection<>(neighbourX, neighbourZ, this, 1); ++ if (null != this.sections.put(neighbourKey, neighbourSection)) { ++ throw new IllegalStateException("Failed to insert new section"); ++ } ++ newSections.add(neighbourSection); ++ } ++ } ++ ++ if (newSections.isEmpty()) { ++ // if we didn't add any sections, then we don't need to merge any regions or create a region ++ return; ++ } ++ ++ final ThreadedRegion regionOfInterest; ++ final boolean regionOfInterestAlive; ++ if (nearbyRegions == null) { ++ // we can simply create a new region, don't have neighbours to worry about merging into ++ regionOfInterest = new ThreadedRegion<>(this); ++ regionOfInterestAlive = true; ++ ++ for (int i = 0, len = newSections.size(); i < len; ++i) { ++ regionOfInterest.addSection(newSections.get(i)); ++ } ++ ++ // only call create callback after adding sections ++ regionOfInterest.onCreate(); ++ } else { ++ // need to merge the regions ++ ThreadedRegion firstUnlockedRegion = null; ++ ++ for (final ThreadedRegion region : nearbyRegions) { ++ if (region.isTicking()) { ++ continue; ++ } ++ firstUnlockedRegion = region; ++ if (firstUnlockedRegion.state == ThreadedRegion.STATE_READY && (!firstUnlockedRegion.mergeIntoLater.isEmpty() || !firstUnlockedRegion.expectingMergeFrom.isEmpty())) { ++ throw new IllegalStateException("Illegal state for unlocked region " + firstUnlockedRegion); ++ } ++ break; ++ } ++ ++ if (firstUnlockedRegion != null) { ++ regionOfInterest = firstUnlockedRegion; ++ } else { ++ regionOfInterest = new ThreadedRegion<>(this); ++ } ++ ++ for (int i = 0, len = newSections.size(); i < len; ++i) { ++ regionOfInterest.addSection(newSections.get(i)); ++ } ++ ++ // only call create callback after adding sections ++ if (firstUnlockedRegion == null) { ++ regionOfInterest.onCreate(); ++ } ++ ++ if (firstUnlockedRegion != null && nearbyRegions.size() == 1) { ++ // nothing to do further, no need to merge anything ++ return; ++ } ++ ++ // we need to now tell all the other regions to merge into the region we just created, ++ // and to merge all the ones we can immediately ++ ++ for (final ThreadedRegion region : nearbyRegions) { ++ if (region == regionOfInterest) { ++ continue; ++ } ++ ++ if (!region.killAndMergeInto(regionOfInterest)) { ++ // note: the region may already be a merge target ++ regionOfInterest.mergeIntoLater(region); ++ } ++ } ++ ++ if (firstUnlockedRegion != null && firstUnlockedRegion.state == ThreadedRegion.STATE_READY) { ++ // we need to retire this region if the merges added other pending merges ++ if (!firstUnlockedRegion.mergeIntoLater.isEmpty() || !firstUnlockedRegion.expectingMergeFrom.isEmpty()) { ++ firstUnlockedRegion.state = ThreadedRegion.STATE_TRANSIENT; ++ this.callbacks.onRegionInactive(firstUnlockedRegion); ++ } ++ } ++ ++ // need to set alive if we created it and there are no pending merges ++ regionOfInterestAlive = firstUnlockedRegion == null && regionOfInterest.mergeIntoLater.isEmpty() && regionOfInterest.expectingMergeFrom.isEmpty(); ++ } ++ ++ if (regionOfInterestAlive) { ++ regionOfInterest.state = ThreadedRegion.STATE_READY; ++ if (!regionOfInterest.mergeIntoLater.isEmpty() || !regionOfInterest.expectingMergeFrom.isEmpty()) { ++ throw new IllegalStateException("Should not happen on region " + this); ++ } ++ this.callbacks.onRegionActive(regionOfInterest); ++ } ++ ++ if (regionOfInterest.state == ThreadedRegion.STATE_READY) { ++ if (!regionOfInterest.mergeIntoLater.isEmpty() || !regionOfInterest.expectingMergeFrom.isEmpty()) { ++ throw new IllegalStateException("Should not happen on region " + this); ++ } ++ } ++ } catch (final Throwable throwable) { ++ LOGGER.error("Failed to add chunk (" + chunkX + "," + chunkZ + ")", throwable); ++ SneakyThrow.sneaky(throwable); ++ return; // unreachable ++ } finally { ++ this.releaseWriteLock(); ++ } ++ } ++ ++ public void removeChunk(final int chunkX, final int chunkZ) { ++ final int sectionX = chunkX >> this.sectionChunkShift; ++ final int sectionZ = chunkZ >> this.sectionChunkShift; ++ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); ++ ++ // Given that for each section, no addChunk/removeChunk can occur in parallel, ++ // we can avoid the lock IF the section exists AND it has a chunk count > 1 ++ final ThreadedRegionSection section = this.sections.get(sectionKey); ++ if (section == null) { ++ throw new IllegalStateException("Chunk (" + chunkX + "," + chunkZ + ") has no section"); ++ } ++ if (!section.hasOnlyOneChunk()) { ++ // chunk will not go empty, so we don't need to acquire the lock ++ section.removeChunk(chunkX, chunkZ); ++ return; ++ } ++ ++ this.acquireWriteLock(); ++ try { ++ section.removeChunk(chunkX, chunkZ); ++ ++ final int searchRadius = this.emptySectionCreateRadius; ++ for (int dx = -searchRadius; dx <= searchRadius; ++dx) { ++ for (int dz = -searchRadius; dz <= searchRadius; ++dz) { ++ if ((dx | dz) == 0) { ++ continue; ++ } ++ ++ final int neighbourX = dx + sectionX; ++ final int neighbourZ = dz + sectionZ; ++ final long neighbourKey = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); ++ ++ final ThreadedRegionSection neighbourSection = this.sections.get(neighbourKey); ++ ++ // should be non-null here always ++ neighbourSection.decrementNonEmptyNeighbours(); ++ } ++ } ++ } catch (final Throwable throwable) { ++ LOGGER.error("Failed to add chunk (" + chunkX + "," + chunkZ + ")", throwable); ++ SneakyThrow.sneaky(throwable); ++ return; // unreachable ++ } finally { ++ this.releaseWriteLock(); ++ } ++ } ++ ++ // must hold regionLock ++ private void onRegionRelease(final ThreadedRegion region) { ++ if (!region.mergeIntoLater.isEmpty()) { ++ throw new IllegalStateException("Region " + region + " should not have any regions to merge into!"); ++ } ++ ++ final boolean hasExpectingMerges = !region.expectingMergeFrom.isEmpty(); ++ ++ // is this region supposed to merge into any other region? ++ if (hasExpectingMerges) { ++ // merge the regions into this one ++ final ReferenceOpenHashSet> expectingMergeFrom = region.expectingMergeFrom.clone(); ++ for (final ThreadedRegion mergeFrom : expectingMergeFrom) { ++ if (!mergeFrom.killAndMergeInto(region)) { ++ throw new IllegalStateException("Merge from region " + mergeFrom + " should be killable! Trying to merge into " + region); ++ } ++ } ++ ++ if (!region.expectingMergeFrom.isEmpty()) { ++ throw new IllegalStateException("Region " + region + " should no longer have merge requests after mering from " + expectingMergeFrom); ++ } ++ ++ if (!region.mergeIntoLater.isEmpty()) { ++ // There is another nearby ticking region that we need to merge into ++ region.state = ThreadedRegion.STATE_TRANSIENT; ++ this.callbacks.onRegionInactive(region); ++ // return to avoid removing dead sections or splitting, these actions will be performed ++ // by the region we merge into ++ return; ++ } ++ } ++ ++ // now check whether we need to recalculate regions ++ final boolean removeDeadSections = hasExpectingMerges || region.hasNoAliveSections() ++ || (region.sectionByKey.size() >= this.minSectionRecalcCount && region.getDeadSectionPercent() >= this.maxDeadRegionPercent); ++ final boolean removedDeadSections = removeDeadSections && !region.deadSections.isEmpty(); ++ if (removeDeadSections) { ++ // kill dead sections ++ for (final ThreadedRegionSection deadSection : region.deadSections) { ++ final long key = CoordinateUtils.getChunkKey(deadSection.sectionX, deadSection.sectionZ); ++ ++ if (!deadSection.isEmpty()) { ++ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!"); ++ } ++ if (deadSection.hasNonEmptyNeighbours()) { ++ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has non-empty neighbours!"); ++ } ++ if (!region.sectionByKey.remove(key, deadSection)) { ++ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection); ++ } ++ if (this.sections.remove(key) != deadSection) { ++ throw new IllegalStateException("Cannot remove dead section '" + ++ deadSection.toStringWithRegion() + "' from section state! State at section coordinate: " + this.sections.get(key)); ++ } ++ } ++ region.deadSections.clear(); ++ } ++ ++ // if we removed dead sections, we should check if the region can be split into smaller ones ++ // otherwise, the region remains alive ++ if (!removedDeadSections) { ++ // didn't remove dead sections, don't check for split ++ region.state = ThreadedRegion.STATE_READY; ++ if (!region.expectingMergeFrom.isEmpty() || !region.mergeIntoLater.isEmpty()) { ++ throw new IllegalStateException("Illegal state " + region); ++ } ++ return; ++ } ++ ++ // first, we need to build copy of coordinate->section map of all sections in recalculate ++ final Long2ReferenceOpenHashMap> recalculateSections = region.sectionByKey.clone(); ++ ++ if (recalculateSections.isEmpty()) { ++ // looks like the region's sections were all dead, and now there is no region at all ++ region.state = ThreadedRegion.STATE_DEAD; ++ region.onRemove(true); ++ return; ++ } ++ ++ // merge radius is max, since recalculateSections includes the dead or empty sections ++ final int mergeRadius = Math.max(this.regionSectionMergeRadius, this.emptySectionCreateRadius); ++ ++ final List>> newRegions = new ArrayList<>(); ++ while (!recalculateSections.isEmpty()) { ++ // select any section, then BFS around it to find all of its neighbours to form a region ++ // once no more neighbours are found, the region is complete ++ final List> currRegion = new ArrayList<>(); ++ final Iterator> firstIterator = recalculateSections.values().iterator(); ++ ++ currRegion.add(firstIterator.next()); ++ firstIterator.remove(); ++ search_loop: ++ for (int idx = 0; idx < currRegion.size(); ++idx) { ++ final ThreadedRegionSection curr = currRegion.get(idx); ++ final int centerX = curr.sectionX; ++ final int centerZ = curr.sectionZ; ++ ++ // find neighbours in radius ++ for (int dz = -mergeRadius; dz <= mergeRadius; ++dz) { ++ for (int dx = -mergeRadius; dx <= mergeRadius; ++dx) { ++ if ((dx | dz) == 0) { ++ continue; ++ } ++ ++ final ThreadedRegionSection section = recalculateSections.remove(CoordinateUtils.getChunkKey(dx + centerX, dz + centerZ)); ++ if (section == null) { ++ continue; ++ } ++ ++ currRegion.add(section); ++ ++ if (recalculateSections.isEmpty()) { ++ // no point in searching further ++ break search_loop; ++ } ++ } ++ } ++ } ++ ++ newRegions.add(currRegion); ++ } ++ ++ // now we have split the regions into separate parts, we can split recalculate ++ ++ if (newRegions.size() == 1) { ++ // no need to split anything, we're done here ++ region.state = ThreadedRegion.STATE_READY; ++ if (!region.expectingMergeFrom.isEmpty() || !region.mergeIntoLater.isEmpty()) { ++ throw new IllegalStateException("Illegal state " + region); ++ } ++ return; ++ } ++ ++ final List> newRegionObjects = new ArrayList<>(newRegions.size()); ++ for (int i = 0, len = newRegions.size(); i < len; ++i) { ++ newRegionObjects.add(new ThreadedRegion<>(this)); ++ } ++ ++ this.callbacks.preSplit(region, newRegionObjects); ++ ++ // need to split the region, so we need to kill the old one first ++ region.state = ThreadedRegion.STATE_DEAD; ++ region.onRemove(true); ++ ++ // create new regions ++ final Long2ReferenceOpenHashMap> newRegionsMap = new Long2ReferenceOpenHashMap<>(); ++ final ReferenceOpenHashSet> newRegionsSet = new ReferenceOpenHashSet<>(newRegionObjects); ++ ++ for (int i = 0, len = newRegions.size(); i < len; i++) { ++ final List> sections = newRegions.get(i); ++ final ThreadedRegion newRegion = newRegionObjects.get(i); ++ ++ for (final ThreadedRegionSection section : sections) { ++ section.setRegionRelease(null); ++ newRegion.addSection(section); ++ final ThreadedRegion curr = newRegionsMap.putIfAbsent(section.sectionKey, newRegion); ++ if (curr != null) { ++ throw new IllegalStateException("Expected no region at " + section + ", but got " + curr + ", should have put " + newRegion); ++ } ++ } ++ } ++ ++ region.split(newRegionsMap, newRegionsSet); ++ ++ // only after invoking data callbacks ++ ++ for (final ThreadedRegion newRegion : newRegionsSet) { ++ newRegion.state = ThreadedRegion.STATE_READY; ++ if (!newRegion.expectingMergeFrom.isEmpty() || !newRegion.mergeIntoLater.isEmpty()) { ++ throw new IllegalStateException("Illegal state " + newRegion); ++ } ++ newRegion.onCreate(); ++ this.callbacks.onRegionActive(newRegion); ++ } ++ } ++ ++ public static final class ThreadedRegion, S extends ThreadedRegionSectionData> { ++ ++ private static final AtomicLong REGION_ID_GENERATOR = new AtomicLong(); ++ ++ private static final int STATE_TRANSIENT = 0; ++ private static final int STATE_READY = 1; ++ private static final int STATE_TICKING = 2; ++ private static final int STATE_DEAD = 3; ++ ++ public final long id; ++ ++ private int state; ++ ++ private final Long2ReferenceOpenHashMap> sectionByKey = new Long2ReferenceOpenHashMap<>(); ++ private final ReferenceOpenHashSet> deadSections = new ReferenceOpenHashSet<>(); ++ ++ public final ThreadedRegionizer regioniser; ++ ++ private final R data; ++ ++ private final ReferenceOpenHashSet> mergeIntoLater = new ReferenceOpenHashSet<>(); ++ private final ReferenceOpenHashSet> expectingMergeFrom = new ReferenceOpenHashSet<>(); ++ ++ public ThreadedRegion(final ThreadedRegionizer regioniser) { ++ this.regioniser = regioniser; ++ this.id = REGION_ID_GENERATOR.getAndIncrement(); ++ this.state = STATE_TRANSIENT; ++ this.data = regioniser.callbacks.createNewData(this); ++ } ++ ++ public LongArrayList getOwnedSections() { ++ final boolean lock = this.regioniser.writeLockOwner != Thread.currentThread(); ++ if (lock) { ++ this.regioniser.regionLock.readLock(); ++ } ++ try { ++ final LongArrayList ret = new LongArrayList(this.sectionByKey.size()); ++ ret.addAll(this.sectionByKey.keySet()); ++ ++ return ret; ++ } finally { ++ if (lock) { ++ this.regioniser.regionLock.tryUnlockRead(); ++ } ++ } ++ } ++ ++ /** ++ * returns an iterator directly over the sections map. This is only to be used by a thread which is _ticking_ ++ * 'this' region. ++ */ ++ public LongIterator getOwnedSectionsUnsynchronised() { ++ return this.sectionByKey.keySet().iterator(); ++ } ++ ++ public LongArrayList getOwnedChunks() { ++ final boolean lock = this.regioniser.writeLockOwner != Thread.currentThread(); ++ if (lock) { ++ this.regioniser.regionLock.readLock(); ++ } ++ try { ++ final LongArrayList ret = new LongArrayList(); ++ for (final ThreadedRegionSection section : this.sectionByKey.values()) { ++ ret.addAll(section.getChunks()); ++ } ++ ++ return ret; ++ } finally { ++ if (lock) { ++ this.regioniser.regionLock.tryUnlockRead(); ++ } ++ } ++ } ++ ++ public Long getCenterSection() { ++ final LongArrayList sections = this.getOwnedSections(); ++ ++ final LongComparator comparator = (final long k1, final long k2) -> { ++ final int x1 = CoordinateUtils.getChunkX(k1); ++ final int x2 = CoordinateUtils.getChunkX(k2); ++ ++ final int z1 = CoordinateUtils.getChunkZ(x1); ++ final int z2 = CoordinateUtils.getChunkZ(x2); ++ ++ final int zCompare = Integer.compare(z1, z2); ++ if (zCompare != 0) { ++ return zCompare; ++ } ++ ++ return Integer.compare(x1, x2); ++ }; ++ ++ // note: regions don't always have a chunk section at this point, because the region may have been killed ++ if (sections.isEmpty()) { ++ return null; ++ } ++ ++ sections.sort(comparator); ++ ++ return Long.valueOf(sections.getLong(sections.size() >> 1)); ++ } ++ ++ public ChunkPos getCenterChunk() { ++ final LongArrayList chunks = this.getOwnedChunks(); ++ ++ final LongComparator comparator = (final long k1, final long k2) -> { ++ final int x1 = CoordinateUtils.getChunkX(k1); ++ final int x2 = CoordinateUtils.getChunkX(k2); ++ ++ final int z1 = CoordinateUtils.getChunkZ(k1); ++ final int z2 = CoordinateUtils.getChunkZ(k2); ++ ++ final int zCompare = Integer.compare(z1, z2); ++ if (zCompare != 0) { ++ return zCompare; ++ } ++ ++ return Integer.compare(x1, x2); ++ }; ++ chunks.sort(comparator); ++ ++ // note: regions don't always have a chunk at this point, because the region may have been killed ++ if (chunks.isEmpty()) { ++ return null; ++ } ++ ++ final long middle = chunks.getLong(chunks.size() >> 1); ++ ++ return new ChunkPos(CoordinateUtils.getChunkX(middle), CoordinateUtils.getChunkZ(middle)); ++ } ++ ++ private void onCreate() { ++ this.regioniser.onRegionCreate(this); ++ this.regioniser.callbacks.onRegionCreate(this); ++ } ++ ++ private void onRemove(final boolean wasActive) { ++ if (wasActive) { ++ this.regioniser.callbacks.onRegionInactive(this); ++ } ++ this.regioniser.callbacks.onRegionDestroy(this); ++ this.regioniser.onRegionDestroy(this); ++ } ++ ++ private final boolean hasNoAliveSections() { ++ return this.deadSections.size() == this.sectionByKey.size(); ++ } ++ ++ private final double getDeadSectionPercent() { ++ return (double)this.deadSections.size() / (double)this.sectionByKey.size(); ++ } ++ ++ private void split(final Long2ReferenceOpenHashMap> into, final ReferenceOpenHashSet> regions) { ++ if (this.data != null) { ++ this.data.split(this.regioniser, into, regions); ++ } ++ } ++ ++ boolean killAndMergeInto(final ThreadedRegion mergeTarget) { ++ if (this.state == STATE_TICKING) { ++ return false; ++ } ++ ++ this.regioniser.callbacks.preMerge(this, mergeTarget); ++ ++ this.tryKill(); ++ ++ this.mergeInto(mergeTarget); ++ ++ return true; ++ } ++ ++ private void mergeInto(final ThreadedRegion mergeTarget) { ++ if (this == mergeTarget) { ++ throw new IllegalStateException("Cannot merge a region onto itself"); ++ } ++ if (!this.isDead()) { ++ throw new IllegalStateException("Source region is not dead! Source " + this + ", target " + mergeTarget); ++ } else if (mergeTarget.isDead()) { ++ throw new IllegalStateException("Target region is dead! Source " + this + ", target " + mergeTarget); ++ } ++ ++ for (final ThreadedRegionSection section : this.sectionByKey.values()) { ++ section.setRegionRelease(null); ++ mergeTarget.addSection(section); ++ } ++ for (final ThreadedRegionSection deadSection : this.deadSections) { ++ if (this.sectionByKey.get(deadSection.sectionKey) != deadSection) { ++ throw new IllegalStateException("Source region does not even contain its own dead sections! Missing " + deadSection + " from region " + this); ++ } ++ if (!mergeTarget.deadSections.add(deadSection)) { ++ throw new IllegalStateException("Merge target contains dead section from source! Has " + deadSection + " from region " + this); ++ } ++ } ++ ++ // forward merge expectations ++ for (final ThreadedRegion region : this.expectingMergeFrom) { ++ if (!region.mergeIntoLater.remove(this)) { ++ throw new IllegalStateException("Region " + region + " was not supposed to merge into " + this + "?"); ++ } ++ if (region != mergeTarget) { ++ region.mergeIntoLater(mergeTarget); ++ } ++ } ++ ++ // forward merge into ++ for (final ThreadedRegion region : this.mergeIntoLater) { ++ if (!region.expectingMergeFrom.remove(this)) { ++ throw new IllegalStateException("Region " + this + " was not supposed to merge into " + region + "?"); ++ } ++ if (region != mergeTarget) { ++ mergeTarget.mergeIntoLater(region); ++ } ++ } ++ ++ // finally, merge data ++ if (this.data != null) { ++ this.data.mergeInto(mergeTarget); ++ } ++ } ++ ++ private void mergeIntoLater(final ThreadedRegion region) { ++ if (region.isDead()) { ++ throw new IllegalStateException("Trying to merge later into a dead region: " + region); ++ } ++ final boolean add1, add2; ++ if ((add1 = this.mergeIntoLater.add(region)) != (add2 = region.expectingMergeFrom.add(this))) { ++ throw new IllegalStateException("Inconsistent state between target merge " + region + " and this " + this + ": add1,add2:" + add1 + "," + add2); ++ } ++ } ++ ++ private boolean tryKill() { ++ switch (this.state) { ++ case STATE_TRANSIENT: { ++ this.state = STATE_DEAD; ++ this.onRemove(false); ++ return true; ++ } ++ case STATE_READY: { ++ this.state = STATE_DEAD; ++ this.onRemove(true); ++ return true; ++ } ++ case STATE_TICKING: { ++ return false; ++ } ++ case STATE_DEAD: { ++ throw new IllegalStateException("Already dead"); ++ } ++ default: { ++ throw new IllegalStateException("Unknown state: " + this.state); ++ } ++ } ++ } ++ ++ private boolean isDead() { ++ return this.state == STATE_DEAD; ++ } ++ ++ private boolean isTicking() { ++ return this.state == STATE_TICKING; ++ } ++ ++ private void removeDeadSection(final ThreadedRegionSection section) { ++ this.deadSections.remove(section); ++ } ++ ++ private void addDeadSection(final ThreadedRegionSection section) { ++ this.deadSections.add(section); ++ } ++ ++ private void addSection(final ThreadedRegionSection section) { ++ if (section.getRegionPlain() != null) { ++ throw new IllegalStateException("Section already has region"); ++ } ++ if (this.sectionByKey.putIfAbsent(section.sectionKey, section) != null) { ++ throw new IllegalStateException("Already have section " + section + ", mapped to " + this.sectionByKey.get(section.sectionKey)); ++ } ++ section.setRegionRelease(this); ++ } ++ ++ public R getData() { ++ return this.data; ++ } ++ ++ public boolean tryMarkTicking(final BooleanSupplier abort) { ++ this.regioniser.acquireWriteLock(); ++ try { ++ if (this.state != STATE_READY || abort.getAsBoolean()) { ++ return false; ++ } ++ ++ if (!this.mergeIntoLater.isEmpty() || !this.expectingMergeFrom.isEmpty()) { ++ throw new IllegalStateException("Region " + this + " should not be ready"); ++ } ++ ++ this.state = STATE_TICKING; ++ return true; ++ } finally { ++ this.regioniser.releaseWriteLock(); ++ } ++ } ++ ++ public boolean markNotTicking() { ++ this.regioniser.acquireWriteLock(); ++ try { ++ if (this.state != STATE_TICKING) { ++ throw new IllegalStateException("Attempting to release non-locked state"); ++ } ++ ++ this.regioniser.onRegionRelease(this); ++ ++ return this.state == STATE_READY; ++ } catch (final Throwable throwable) { ++ LOGGER.error("Failed to release region " + this, throwable); ++ SneakyThrow.sneaky(throwable); ++ return false; // unreachable ++ } finally { ++ this.regioniser.releaseWriteLock(); ++ } ++ } ++ ++ @Override ++ public String toString() { ++ final StringBuilder ret = new StringBuilder(128); ++ ++ ret.append("ThreadedRegion{"); ++ ret.append("state=").append(this.state).append(','); ++ // To avoid recursion in toString, maybe fix later? ++ //ret.append("mergeIntoLater=").append(this.mergeIntoLater).append(','); ++ //ret.append("expectingMergeFrom=").append(this.expectingMergeFrom).append(','); ++ ++ ret.append("sectionCount=").append(this.sectionByKey.size()).append(','); ++ ret.append("sections=["); ++ for (final Iterator> iterator = this.sectionByKey.values().iterator(); iterator.hasNext();) { ++ final ThreadedRegionSection section = iterator.next(); ++ ++ ret.append(section.toString()); ++ if (iterator.hasNext()) { ++ ret.append(','); ++ } ++ } ++ ret.append(']'); ++ ++ ret.append('}'); ++ return ret.toString(); ++ } ++ } ++ ++ public static final class ThreadedRegionSection, S extends ThreadedRegionSectionData> { ++ ++ public final int sectionX; ++ public final int sectionZ; ++ public final long sectionKey; ++ private final long[] chunksBitset; ++ private int chunkCount; ++ private int nonEmptyNeighbours; ++ ++ private ThreadedRegion region; ++ private static final VarHandle REGION_HANDLE = ConcurrentUtil.getVarHandle(ThreadedRegionSection.class, "region", ThreadedRegion.class); ++ ++ public final ThreadedRegionizer regioniser; ++ ++ private final int regionChunkShift; ++ private final int regionChunkMask; ++ ++ private final S data; ++ ++ private ThreadedRegion getRegionPlain() { ++ return (ThreadedRegion)REGION_HANDLE.get(this); ++ } ++ ++ private ThreadedRegion getRegionAcquire() { ++ return (ThreadedRegion)REGION_HANDLE.getAcquire(this); ++ } ++ ++ private void setRegionRelease(final ThreadedRegion value) { ++ REGION_HANDLE.setRelease(this, value); ++ } ++ ++ // creates an empty section with zero non-empty neighbours ++ private ThreadedRegionSection(final int sectionX, final int sectionZ, final ThreadedRegionizer regioniser) { ++ this.sectionX = sectionX; ++ this.sectionZ = sectionZ; ++ this.sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); ++ this.chunksBitset = new long[Math.max(1, regioniser.regionSectionChunkSize * regioniser.regionSectionChunkSize / Long.SIZE)]; ++ this.regioniser = regioniser; ++ this.regionChunkShift = regioniser.sectionChunkShift; ++ this.regionChunkMask = regioniser.regionSectionChunkSize - 1; ++ this.data = regioniser.callbacks ++ .createNewSectionData(sectionX, sectionZ, this.regionChunkShift); ++ } ++ ++ // creates a section with an initial chunk with zero non-empty neighbours ++ private ThreadedRegionSection(final int sectionX, final int sectionZ, final ThreadedRegionizer regioniser, ++ final int chunkXInit, final int chunkZInit) { ++ this(sectionX, sectionZ, regioniser); ++ ++ final int initIndex = this.getChunkIndex(chunkXInit, chunkZInit); ++ this.chunkCount = 1; ++ this.chunksBitset[initIndex >>> 6] = 1L << (initIndex & (Long.SIZE - 1)); // index / Long.SIZE ++ } ++ ++ // creates an empty section with the specified number of non-empty neighbours ++ private ThreadedRegionSection(final int sectionX, final int sectionZ, final ThreadedRegionizer regioniser, ++ final int nonEmptyNeighbours) { ++ this(sectionX, sectionZ, regioniser); ++ ++ this.nonEmptyNeighbours = nonEmptyNeighbours; ++ } ++ ++ public LongArrayList getChunks() { ++ final LongArrayList ret = new LongArrayList(); ++ ++ if (this.chunkCount == 0) { ++ return ret; ++ } ++ ++ final int shift = this.regionChunkShift; ++ final int mask = this.regionChunkMask; ++ final int offsetX = this.sectionX << shift; ++ final int offsetZ = this.sectionZ << shift; ++ ++ final long[] bitset = this.chunksBitset; ++ for (int arrIdx = 0, arrLen = bitset.length; arrIdx < arrLen; ++arrIdx) { ++ long value = bitset[arrIdx]; ++ ++ for (int i = 0, bits = Long.bitCount(value); i < bits; ++i) { ++ final int valueIdx = Long.numberOfTrailingZeros(value); ++ value ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(value); ++ ++ final int idx = valueIdx | (arrIdx << 6); ++ ++ final int localX = idx & mask; ++ final int localZ = (idx >>> shift) & mask; ++ ++ ret.add(CoordinateUtils.getChunkKey(localX | offsetX, localZ | offsetZ)); ++ } ++ } ++ ++ return ret; ++ } ++ ++ private boolean isEmpty() { ++ return this.chunkCount == 0; ++ } ++ ++ private boolean hasOnlyOneChunk() { ++ return this.chunkCount == 1; ++ } ++ ++ public boolean hasNonEmptyNeighbours() { ++ return this.nonEmptyNeighbours != 0; ++ } ++ ++ /** ++ * Returns the section data associated with this region section. May be {@code null}. ++ */ ++ public S getData() { ++ return this.data; ++ } ++ ++ /** ++ * Returns the region that owns this section. Unsynchronised access may produce outdateed or transient results. ++ */ ++ public ThreadedRegion getRegion() { ++ return this.getRegionAcquire(); ++ } ++ ++ private int getChunkIndex(final int chunkX, final int chunkZ) { ++ return (chunkX & this.regionChunkMask) | ((chunkZ & this.regionChunkMask) << this.regionChunkShift); ++ } ++ ++ private void markAlive() { ++ this.getRegionPlain().removeDeadSection(this); ++ } ++ ++ private void markDead() { ++ this.getRegionPlain().addDeadSection(this); ++ } ++ ++ private void incrementNonEmptyNeighbours() { ++ if (++this.nonEmptyNeighbours == 1 && this.chunkCount == 0) { ++ this.markAlive(); ++ } ++ final int createRadius = this.regioniser.emptySectionCreateRadius; ++ if (this.nonEmptyNeighbours >= ((createRadius * 2 + 1) * (createRadius * 2 + 1))) { ++ throw new IllegalStateException("Non empty neighbours exceeded max value for radius " + createRadius); ++ } ++ } ++ ++ private void decrementNonEmptyNeighbours() { ++ if (--this.nonEmptyNeighbours == 0 && this.chunkCount == 0) { ++ this.markDead(); ++ } ++ if (this.nonEmptyNeighbours < 0) { ++ throw new IllegalStateException("Non empty neighbours reached zero"); ++ } ++ } ++ ++ /** ++ * Returns whether the chunk was zero. Effectively returns whether the caller needs to create ++ * dead sections / increase non-empty neighbour count for neighbouring sections. ++ */ ++ private boolean addChunk(final int chunkX, final int chunkZ) { ++ final int index = this.getChunkIndex(chunkX, chunkZ); ++ final long bitset = this.chunksBitset[index >>> 6]; // index / Long.SIZE ++ final long after = this.chunksBitset[index >>> 6] = bitset | (1L << (index & (Long.SIZE - 1))); ++ if (after == bitset) { ++ throw new IllegalStateException("Cannot add a chunk to a section which already has the chunk! RegionSection: " + this + ", global chunk: " + new ChunkPos(chunkX, chunkZ).toString()); ++ } ++ final boolean notEmpty = ++this.chunkCount == 1; ++ if (notEmpty && this.nonEmptyNeighbours == 0) { ++ this.markAlive(); ++ } ++ return notEmpty; ++ } ++ ++ /** ++ * Returns whether the chunk count is now zero. Effectively returns whether ++ * the caller needs to decrement the neighbour count for neighbouring sections. ++ */ ++ private boolean removeChunk(final int chunkX, final int chunkZ) { ++ final int index = this.getChunkIndex(chunkX, chunkZ); ++ final long before = this.chunksBitset[index >>> 6]; // index / Long.SIZE ++ final long bitset = this.chunksBitset[index >>> 6] = before & ~(1L << (index & (Long.SIZE - 1))); ++ if (before == bitset) { ++ throw new IllegalStateException("Cannot remove a chunk from a section which does not have that chunk! RegionSection: " + this + ", global chunk: " + new ChunkPos(chunkX, chunkZ).toString()); ++ } ++ final boolean empty = --this.chunkCount == 0; ++ if (empty && this.nonEmptyNeighbours == 0) { ++ this.markDead(); ++ } ++ return empty; ++ } ++ ++ @Override ++ public String toString() { ++ return "RegionSection{" + ++ "sectionCoordinate=" + new ChunkPos(this.sectionX, this.sectionZ).toString() + "," + ++ "chunkCount=" + this.chunkCount + "," + ++ "chunksBitset=" + toString(this.chunksBitset) + "," + ++ "nonEmptyNeighbours=" + this.nonEmptyNeighbours + "," + ++ "hash=" + this.hashCode() + ++ "}"; ++ } ++ ++ public String toStringWithRegion() { ++ return "RegionSection{" + ++ "sectionCoordinate=" + new ChunkPos(this.sectionX, this.sectionZ).toString() + "," + ++ "chunkCount=" + this.chunkCount + "," + ++ "chunksBitset=" + toString(this.chunksBitset) + "," + ++ "hash=" + this.hashCode() + "," + ++ "nonEmptyNeighbours=" + this.nonEmptyNeighbours + "," + ++ "region=" + this.getRegionAcquire() + ++ "}"; ++ } ++ ++ private static String toString(final long[] array) { ++ final StringBuilder ret = new StringBuilder(); ++ final char[] zeros = new char[Long.SIZE / 4]; ++ for (final long value : array) { ++ // zero pad the hex string ++ Arrays.fill(zeros, '0'); ++ final String string = Long.toHexString(value); ++ System.arraycopy(string.toCharArray(), 0, zeros, zeros.length - string.length(), string.length()); ++ ++ ret.append(zeros); ++ } ++ ++ return ret.toString(); ++ } ++ } ++ ++ public static interface ThreadedRegionData, S extends ThreadedRegionSectionData> { ++ ++ /** ++ * Splits this region data into the specified regions set. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param regioniser Regioniser for which the regions reside in. ++ * @param into A map of region section coordinate key to the region that owns the section. ++ * @param regions The set of regions to split into. ++ */ ++ public void split(final ThreadedRegionizer regioniser, final Long2ReferenceOpenHashMap> into, ++ final ReferenceOpenHashSet> regions); ++ ++ /** ++ * Callback to merge {@code this} region data into the specified region. The state of the region is undefined ++ * except that its region data is already created. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param into Specified region. ++ */ ++ public void mergeInto(final ThreadedRegion into); ++ } ++ ++ public static interface ThreadedRegionSectionData {} ++ ++ public static interface RegionCallbacks, S extends ThreadedRegionSectionData> { ++ ++ /** ++ * Creates new section data for the specified section x and section z. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param sectionX x coordinate of the section. ++ * @param sectionZ z coordinate of the section. ++ * @param sectionShift The signed right shift value that can be applied to any chunk coordinate that ++ * produces a section coordinate. ++ * @return New section data, may be {@code null}. ++ */ ++ public S createNewSectionData(final int sectionX, final int sectionZ, final int sectionShift); ++ ++ /** ++ * Creates new region data for the specified region. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param forRegion The region to create the data for. ++ * @return New region data, may be {@code null}. ++ */ ++ public R createNewData(final ThreadedRegion forRegion); ++ ++ /** ++ * Callback for when a region is created. This is invoked after the region is completely set up, ++ * so its data and owned sections are reliable to inspect. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param region The region that was created. ++ */ ++ public void onRegionCreate(final ThreadedRegion region); ++ ++ /** ++ * Callback for when a region is destroyed. This is invoked before the region is actually destroyed; so ++ * its data and owned sections are reliable to inspect. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param region The region that is about to be destroyed. ++ */ ++ public void onRegionDestroy(final ThreadedRegion region); ++ ++ /** ++ * Callback for when a region is considered "active." An active region x is a non-destroyed region which ++ * is not scheduled to merge into another region y and there are no non-destroyed regions z which are ++ * scheduled to merge into the region x. Equivalently, an active region is not directly adjacent to any ++ * other region considering the regioniser's empty section radius. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param region The region that is now active. ++ */ ++ public void onRegionActive(final ThreadedRegion region); ++ ++ /** ++ * Callback for when a region transistions becomes inactive. An inactive region is non-destroyed, but ++ * has neighbouring adjacent regions considering the regioniser's empty section radius. Effectively, ++ * an inactive region may not tick and needs to be merged into its neighbouring regions. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param region The region that is now inactive. ++ */ ++ public void onRegionInactive(final ThreadedRegion region); ++ ++ /** ++ * Callback for when a region (from) is about to be merged into a target region (into). Note that ++ * {@code from} is still alive and is a distinct region. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param from The region that will be merged into the target. ++ * @param into The target of the merge. ++ */ ++ public void preMerge(final ThreadedRegion from, final ThreadedRegion into); ++ ++ /** ++ * Callback for when a region (from) is about to be split into a list of target region (into). Note that ++ * {@code from} is still alive, while the list of target regions are not initialised. ++ *

    ++ * Note: ++ *

    ++ *

    ++ * This function is always called while holding critical locks and as such should not attempt to block on anything, and ++ * should NOT retrieve or modify ANY world state. ++ *

    ++ * @param from The region that will be merged into the target. ++ * @param into The list of regions to split into. ++ */ ++ public void preSplit(final ThreadedRegion from, final List> into); ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/TickData.java b/io/papermc/paper/threadedregions/TickData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d4d80a69488f57704f1b3dc74cb379de36e80ec0 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/TickData.java +@@ -0,0 +1,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 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 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 collapsedData = new ArrayList<>(); ++ for (int i = 0, len = allData.size(); i < len; ++i) { ++ final List 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 ++ ) {} ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..18f57216522a8e22ea6c217c05588f8be13f5c88 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java +@@ -0,0 +1,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 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 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 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). ++ */ ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/TickRegions.java b/io/papermc/paper/threadedregions/TickRegions.java +index 8424cf9d4617b4732d44cc460d25b04481068989..df15b1139e71dfe10b8f24ec6d235b99f6d5006a 100644 +--- a/io/papermc/paper/threadedregions/TickRegions.java ++++ b/io/papermc/paper/threadedregions/TickRegions.java +@@ -1,10 +1,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 { ++ ++ 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 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 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 region) { ++ // nothing for now ++ } ++ ++ @Override ++ public void onRegionActive(final ThreadedRegionizer.ThreadedRegion region) { ++ final TickRegionData data = region.getData(); ++ ++ data.tickHandle.checkInitialSchedule(); ++ scheduler.scheduleRegion(data.tickHandle); ++ } ++ ++ @Override ++ public void onRegionInactive(final ThreadedRegionizer.ThreadedRegion 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 from, ++ final ThreadedRegionizer.ThreadedRegion into) { ++ ++ } ++ ++ @Override ++ public void preSplit(final ThreadedRegionizer.ThreadedRegion from, ++ final java.util.List> 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 { ++ ++ 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 region; ++ public final ServerLevel world; ++ ++ // generic regionised data ++ private final Reference2ReferenceOpenHashMap, 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 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 getRegionizedData(final RegionizedData regionizedData) { ++ return (T)this.regionizedData.get(regionizedData); ++ } ++ ++ T getOrCreateRegionizedData(final RegionizedData 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 regioniser, ++ final Long2ReferenceOpenHashMap> into, ++ final ReferenceOpenHashSet> 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 region : regions) { ++ final TickRegionData data = region.getData(); ++ data.tickHandle.copyDeadlineAndTickCount(this.tickHandle); ++ } ++ ++ // generic regionised data ++ for (final Iterator, Object>> dataIterator = this.regionizedData.reference2ReferenceEntrySet().fastIterator(); ++ dataIterator.hasNext();) { ++ final Reference2ReferenceMap.Entry, Object> regionDataEntry = dataIterator.next(); ++ final RegionizedData data = regionDataEntry.getKey(); ++ final Object from = regionDataEntry.getValue(); ++ ++ final ReferenceOpenHashSet dataSet = new ReferenceOpenHashSet<>(regions.size(), 0.75f); ++ ++ for (final ThreadedRegionizer.ThreadedRegion region : regions) { ++ dataSet.add(region.getData().getOrCreateRegionizedData(data)); ++ } ++ ++ final Long2ReferenceOpenHashMap regionToData = new Long2ReferenceOpenHashMap<>(into.size(), 0.75f); ++ ++ for (final Iterator>> regionIterator = into.long2ReferenceEntrySet().fastIterator(); ++ regionIterator.hasNext();) { ++ final Long2ReferenceMap.Entry> entry = regionIterator.next(); ++ final ThreadedRegionizer.ThreadedRegion region = entry.getValue(); ++ final Object to = region.getData().getOrCreateRegionizedData(data); ++ ++ regionToData.put(entry.getLongKey(), to); ++ } ++ ++ ((RegionizedData)data).getCallback().split(from, shift, regionToData, dataSet); ++ } ++ ++ // chunk holder manager data ++ { ++ final ReferenceOpenHashSet dataSet = new ReferenceOpenHashSet<>(regions.size(), 0.75f); ++ ++ for (final ThreadedRegionizer.ThreadedRegion region : regions) { ++ dataSet.add(region.getData().holderManagerRegionData); ++ } ++ ++ final Long2ReferenceOpenHashMap regionToData = new Long2ReferenceOpenHashMap<>(into.size(), 0.75f); ++ ++ for (final Iterator>> regionIterator = into.long2ReferenceEntrySet().fastIterator(); ++ regionIterator.hasNext();) { ++ final Long2ReferenceMap.Entry> entry = regionIterator.next(); ++ final ThreadedRegionizer.ThreadedRegion 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 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, Object>> iterator = this.regionizedData.reference2ReferenceEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Reference2ReferenceMap.Entry, Object> entry = iterator.next(); ++ final RegionizedData regionizedData = entry.getKey(); ++ final Object from = entry.getValue(); ++ final Object to = into.getData().getOrCreateRegionizedData(regionizedData); ++ ++ ((RegionizedData)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(); ++ } + } + + } +diff --git a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ebe2592a78e996fa2d415663bd6436effec1ca29 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java +@@ -0,0 +1,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 TWO_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { ++ return new DecimalFormat("#,##0.00"); ++ }); ++ private static final ThreadLocal ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { ++ return new DecimalFormat("#,##0.0"); ++ }); ++ private static final ThreadLocal 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("/ [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 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> 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 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 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, 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, TickData.TickReportData> ++ pair = regionsBelowThreshold.get(i); ++ ++ final TickData.TickReportData report = pair.right(); ++ final ThreadedRegionizer.ThreadedRegion 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 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<>(); ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/commands/CommandUtil.java b/io/papermc/paper/threadedregions/commands/CommandUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1054e28e54f55e0a70eb25aee89cbb4898446fa5 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/commands/CommandUtil.java +@@ -0,0 +1,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 getSortedList(final Iterable iterable) { ++ final List ret = new ArrayList<>(); ++ for (final String val : iterable) { ++ ret.add(val); ++ } ++ ++ ret.sort(String.CASE_INSENSITIVE_ORDER); ++ ++ return ret; ++ } ++ ++ public static List getSortedList(final Iterable iterable, final String prefix) { ++ final List 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 List getSortedList(final Iterable iterable, final Function transform) { ++ final List 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 List getSortedList(final Iterable iterable, final Function transform, final String prefix) { ++ final List 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() {} ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java b/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bc8aa525b3488dc71e7ca0529c6a8c57eaa99e1e +--- /dev/null ++++ b/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java +@@ -0,0 +1,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_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 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 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 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 REGIONISER_CALLBACK = new RegionizedData.RegioniserCallback<>() { ++ @Override ++ public void merge(final Scheduler from, final Scheduler into, final long fromTickOffset) { ++ for (final Iterator>>> sectionIterator = from.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator(); ++ sectionIterator.hasNext();) { ++ final Long2ObjectMap.Entry>> entry = sectionIterator.next(); ++ final long sectionKey = entry.getLongKey(); ++ final Long2ObjectOpenHashMap> section = entry.getValue(); ++ ++ final Long2ObjectOpenHashMap> sectionAdjusted = new Long2ObjectOpenHashMap<>(section.size()); ++ ++ for (final Iterator>> iterator = section.long2ObjectEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2ObjectMap.Entry> e = iterator.next(); ++ final long newTick = e.getLongKey() + fromTickOffset; ++ final List tasks = e.getValue(); ++ ++ sectionAdjusted.put(newTick, tasks); ++ } ++ ++ into.tasksByDeadlineBySection.put(sectionKey, sectionAdjusted); ++ } ++ } ++ ++ @Override ++ public void split(final Scheduler from, final int chunkToRegionShift, final Long2ReferenceOpenHashMap regionToData, ++ final ReferenceOpenHashSet dataSet) { ++ for (final Scheduler into : dataSet) { ++ into.tickCount = from.tickCount; ++ } ++ ++ for (final Iterator>>> sectionIterator = from.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator(); ++ sectionIterator.hasNext();) { ++ final Long2ObjectMap.Entry>> entry = sectionIterator.next(); ++ final long sectionKey = entry.getLongKey(); ++ final Long2ObjectOpenHashMap> 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>> 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> 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 run = new ArrayList<>(); ++ ++ for (final Iterator>>> sectionIterator = this.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator(); ++ sectionIterator.hasNext();) { ++ final Long2ObjectMap.Entry>> entry = sectionIterator.next(); ++ final long sectionKey = entry.getLongKey(); ++ final Long2ObjectOpenHashMap> section = entry.getValue(); ++ ++ final List 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 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 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); ++ } ++ } ++ } ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java b/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14d20c996e9b25077f7e51c5d7d432c4a2b01671 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java +@@ -0,0 +1,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{}"); ++ } ++ } ++} +\ No newline at end of file +diff --git a/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java b/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c167179775ec09877808d91eb04b3cdb688c00a4 +--- /dev/null ++++ b/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java +@@ -0,0 +1,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{}"); ++ } ++ } ++} +\ No newline at end of file +diff --git a/net/minecraft/commands/CommandSourceStack.java b/net/minecraft/commands/CommandSourceStack.java +index c2b7164a1395842ab95428540782eeda4c7960b0..d5eefed0912c728ded360ddac4d9bcd1813730b2 100644 +--- a/net/minecraft/commands/CommandSourceStack.java ++++ b/net/minecraft/commands/CommandSourceStack.java +@@ -91,7 +91,7 @@ public class CommandSourceStack implements ExecutionCommandSource { io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(run);}) // Folia - region threading + ); + } + +diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java +index fa8c5ba4e0efd0c36613aaa8eaafba0cb70ceb87..d9b339eaa28aedbc7e7a1d4eebfebbb5ff16afe1 100644 +--- a/net/minecraft/commands/Commands.java ++++ b/net/minecraft/commands/Commands.java +@@ -153,13 +153,13 @@ public class Commands { + 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 +169,47 @@ public class Commands { + 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 +237,8 @@ public class Commands { + 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 +480,12 @@ public class Commands { + } + // Paper start - Perf: Async command map building + new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(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 bukkit, RootCommandNode rootCommandNode) { +diff --git a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +index 4a881636ba21fae9e50950bbba2b4321b71d35ab..af35d667f7dc752df34c49fe675cd0a6cf8ffe4b 100644 +--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +@@ -46,7 +46,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { + 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); + } + +diff --git a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +index bd5bbc7e55c6bea77991fe5a3c0c2580313d16c5..907d3a5385b8b9098051f4ec0887d778fb85cf8d 100644 +--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +@@ -78,7 +78,7 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { + 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); + } + +diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 717c84165d5e25cd384f56b7cb976abf6669b6f0..ebcd1949266f29ca0c99ee26252c366c3f887546 100644 +--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -89,7 +89,7 @@ public interface DispenseItemBehavior { + 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 +147,7 @@ public interface DispenseItemBehavior { + 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 +201,7 @@ public interface DispenseItemBehavior { + 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 +251,7 @@ public interface DispenseItemBehavior { + 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 +329,7 @@ public interface DispenseItemBehavior { + 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 +389,7 @@ public interface DispenseItemBehavior { + 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 +425,7 @@ public interface DispenseItemBehavior { + 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 +482,7 @@ public interface DispenseItemBehavior { + 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 +500,8 @@ public interface DispenseItemBehavior { + } + } + +- 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 +509,13 @@ public interface DispenseItemBehavior { + 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 blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); +- level.capturedBlockStates.clear(); ++ List 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 +549,7 @@ public interface DispenseItemBehavior { + 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 +592,7 @@ public interface DispenseItemBehavior { + 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 +645,7 @@ public interface DispenseItemBehavior { + 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 +703,7 @@ public interface DispenseItemBehavior { + 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 +784,7 @@ public interface DispenseItemBehavior { + 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); + } + +diff --git a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +index 3595bbd05fb3e8fe57e38d4e2df5c6237046b726..9bcb803b761aef0bf29a76bd4bea22f22cbeda5d 100644 +--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +@@ -39,7 +39,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { + 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); + } + +diff --git a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +index 116395b6c00a0814922516707544a9ff26d68835..26c326080ee6fc80f0cc6af3e9fcbc1a508ba01a 100644 +--- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +@@ -62,7 +62,7 @@ public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior { + 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); + } + +diff --git a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +index 449d9b72ff4650961daa9d1bd25940f3914a6b12..b4f2dbe3dcdeac2a297b7909cedd54a8079938d8 100644 +--- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +@@ -32,7 +32,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { + 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); + } + +diff --git a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +index 626e9feb6a6e7a2cbc7c63e30ba4fb6b923e85c7..eb63e114b666128df924dca46235ea8a7edbae54 100644 +--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -25,7 +25,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + 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); + } + +diff --git a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +index 5ab2c8333178335515e619b87ae420f948c83bd1..172f41f15e3f165b8faca85e7bc581082d330041 100644 +--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +@@ -27,7 +27,7 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { + 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); + } + +diff --git a/net/minecraft/gametest/framework/GameTestHelper.java b/net/minecraft/gametest/framework/GameTestHelper.java +index fe4ae6bcdcbb55c47e9f9a4d63ead4c39e6d63cf..36a5ca39214233f37ef7bfeb47331a7deb566e5c 100644 +--- a/net/minecraft/gametest/framework/GameTestHelper.java ++++ b/net/minecraft/gametest/framework/GameTestHelper.java +@@ -306,7 +306,7 @@ public class GameTestHelper { + }; + 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; + } + +diff --git a/net/minecraft/gametest/framework/GameTestServer.java b/net/minecraft/gametest/framework/GameTestServer.java +index 54ca624a8194e7d1c0f3b1c0ddba81165523382c..a8cc20bfad1790f254c4793f09fc4dd3ddd4f25b 100644 +--- a/net/minecraft/gametest/framework/GameTestServer.java ++++ b/net/minecraft/gametest/framework/GameTestServer.java +@@ -175,8 +175,12 @@ public class GameTestServer extends MinecraftServer { + } + + @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); +diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java +index e1000d8ab5ae0034b56a3524d2caee8c299b50e7..5f963104d67f41eba8a11246acac5c45b9c8beae 100644 +--- a/net/minecraft/network/Connection.java ++++ b/net/minecraft/network/Connection.java +@@ -85,7 +85,7 @@ public class Connection extends SimpleChannelInboundHandler> { + private static final ProtocolInfo INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND; + private final PacketFlow receiving; + private volatile boolean sendLoginDisconnect = true; +- private final Queue pendingActions = Queues.newConcurrentLinkedQueue(); // Paper - Optimize network ++ private final Queue 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 +100,7 @@ public class Connection extends SimpleChannelInboundHandler> { + @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 +154,41 @@ public class Connection extends SimpleChannelInboundHandler> { + 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 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 +198,7 @@ public class Connection extends SimpleChannelInboundHandler> { + if (this.delayedDisconnect != null) { + this.disconnect(this.delayedDisconnect); + } ++ this.becomeActive = true; // Folia - region threading + } + + @Override +@@ -434,7 +470,7 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + 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 +499,12 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + public void runOnceConnected(Consumer 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 +555,11 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + 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 +573,61 @@ public class Connection extends SimpleChannelInboundHandler> { + + // 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 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; +- } ++ while (this.canWritePackets()) { ++ final boolean set = this.flushingQueue.getAndSet(true); ++ try { ++ if (set) { ++ // we didn't acquire the lock, break ++ return false; ++ } + +- if (queued.isConsumed()) { +- continue; +- } ++ ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue queue = ++ (ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue)this.pendingActions; ++ WrappedConsumer holder; ++ for (;;) { ++ // synchronise so that queue clears appear atomic ++ synchronized (queue) { ++ holder = queue.pollIf(Connection::canWrite); ++ } ++ if (holder == null) { ++ break; ++ } + +- if (queued instanceof PacketSendAction packetSendAction) { +- final Packet packet = packetSendAction.packet; +- if (!packet.isReady()) { +- return false; ++ holder.accept(this); + } +- } + +- iterator.remove(); +- if (queued.tryMarkConsumed()) { +- queued.accept(this); ++ } finally { ++ if (!set) { ++ this.flushingQueue.set(false); ++ } + } + } ++ + return true; ++ // Folia end - region threading - connection fixes + } + // Paper end - Optimize network + +@@ -590,17 +636,37 @@ public class Connection extends SimpleChannelInboundHandler> { + 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; ++ // 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; ++ } + } +- // Paper end - Buffer joins to world ++ 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 +677,7 @@ public class Connection extends SimpleChannelInboundHandler> { + } // 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 +728,7 @@ public class Connection extends SimpleChannelInboundHandler> { + 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 +920,10 @@ public class Connection extends SimpleChannelInboundHandler> { + + 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 +952,21 @@ public class Connection extends SimpleChannelInboundHandler> { + } + } + // 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 +986,25 @@ public class Connection extends SimpleChannelInboundHandler> { + // Paper start - Optimize network + public void clearPacketQueue() { + final net.minecraft.server.level.ServerPlayer player = getPlayer(); +- for (final Consumer 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 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. +diff --git a/net/minecraft/network/protocol/PacketUtils.java b/net/minecraft/network/protocol/PacketUtils.java +index 4535858701b2bb232b9d2feb2af6551526232ddc..b28ff2f18ab7e0e3a61e37ee46048ab5cb7ab45d 100644 +--- a/net/minecraft/network/protocol/PacketUtils.java ++++ b/net/minecraft/network/protocol/PacketUtils.java +@@ -20,7 +20,7 @@ public class PacketUtils { + + public static void ensureRunningOnSameThread(Packet 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 +43,24 @@ public class PacketUtils { + 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; + } + } +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index ae220a732c78ab076261f20b5a54c71d7fceb407..9c9de462eb7187d6cc3562c796e3bcf69fb20783 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -184,7 +184,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; + // Paper - don't store the vanilla dispatcher +@@ -304,6 +303,50 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 CompletableFuture submit(java.util.function.Supplier task) { ++ if (true) { ++ throw new UnsupportedOperationException(); ++ } ++ return super.submit(task); ++ } ++ ++ @Override ++ public CompletableFuture 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 spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system + AtomicReference atomicReference = new AtomicReference<>(); +@@ -332,46 +375,30 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= MAX_CHUNK_EXEC_TIME) { + if (!moreTasks) { +- this.lastMidTickExecuteFailure = currTime; ++ worldData.lastMidTickExecuteFailure = currTime; // Folia - region threading + } + + // note: negative values reduce the time +@@ -384,7 +411,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> 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 +764,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 +934,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 +1497,24 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; +- } +- // 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 +1534,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 +1609,58 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 +1668,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 +1686,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop players = this.playerList.getPlayers(); ++ List 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 +1760,34 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 +1800,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 +1815,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().invalidateStatus(); ++ }); ++ return; ++ } ++ // Folia end - region threading + this.lastServerStatus = 0L; + } + +@@ -2142,6 +2245,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 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 + } +diff --git a/net/minecraft/server/commands/AdvancementCommands.java b/net/minecraft/server/commands/AdvancementCommands.java +index 9157c1efef669795c8408d2e344a2bfeeabeb842..7873f11d7462ef88b5ba27d99988ac9e45689d3a 100644 +--- a/net/minecraft/server/commands/AdvancementCommands.java ++++ b/net/minecraft/server/commands/AdvancementCommands.java +@@ -246,7 +246,12 @@ public class AdvancementCommands { + 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 +315,12 @@ public class AdvancementCommands { + 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) { +diff --git a/net/minecraft/server/commands/AttributeCommand.java b/net/minecraft/server/commands/AttributeCommand.java +index 2f0e8b2b1dda17cf861f80f8c1e655a345b76d10..505f0ce1f7b453d7e30e07c13a6b7678e12b0fda 100644 +--- a/net/minecraft/server/commands/AttributeCommand.java ++++ b/net/minecraft/server/commands/AttributeCommand.java +@@ -266,30 +266,62 @@ public class AttributeCommand { + } + } + ++ // 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, 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, 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, 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 +329,20 @@ public class AttributeCommand { + "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 getAttributeModifiers(Entity entity, Holder attribute) throws CommandSyntaxException { +@@ -312,11 +351,22 @@ public class AttributeCommand { + } + + private static int setAttributeBase(CommandSourceStack source, Entity entity, Holder 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) throws CommandSyntaxException { +@@ -338,35 +388,57 @@ public class AttributeCommand { + private static int addModifier( + CommandSourceStack source, Entity entity, Holder 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, 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) { +diff --git a/net/minecraft/server/commands/ClearInventoryCommands.java b/net/minecraft/server/commands/ClearInventoryCommands.java +index 73650c835ae3a8709d21462bc91a466167cd115f..3cbeaf2046bb0a41085a00134e69162df46d2081 100644 +--- a/net/minecraft/server/commands/ClearInventoryCommands.java ++++ b/net/minecraft/server/commands/ClearInventoryCommands.java +@@ -65,9 +65,14 @@ public class ClearInventoryCommands { + 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) { +diff --git a/net/minecraft/server/commands/DamageCommand.java b/net/minecraft/server/commands/DamageCommand.java +index d99602f2c7e5463243dfaf83ada12c1d8e7d1192..5f3c886e2bc8a23e902cf8037ac8c871a601883f 100644 +--- a/net/minecraft/server/commands/DamageCommand.java ++++ b/net/minecraft/server/commands/DamageCommand.java +@@ -102,12 +102,29 @@ public class DamageCommand { + ); + } + ++ // 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 + } + } +diff --git a/net/minecraft/server/commands/DefaultGameModeCommands.java b/net/minecraft/server/commands/DefaultGameModeCommands.java +index fd42373ccfedf28ffc0fcf9b3153e5a308c561c5..8a8e51c6a63858df2eae4176df75a66636d3f458 100644 +--- a/net/minecraft/server/commands/DefaultGameModeCommands.java ++++ b/net/minecraft/server/commands/DefaultGameModeCommands.java +@@ -28,12 +28,14 @@ public class DefaultGameModeCommands { + 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++; + } + } +diff --git a/net/minecraft/server/commands/EffectCommands.java b/net/minecraft/server/commands/EffectCommands.java +index 0089ff5ca207278b829ec7530f50ec14681ab574..8d6e1dab63a6ef79d038fc6c3e9f7bf184b1d8c7 100644 +--- a/net/minecraft/server/commands/EffectCommands.java ++++ b/net/minecraft/server/commands/EffectCommands.java +@@ -180,7 +180,12 @@ public class EffectCommands { + 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 +215,12 @@ public class EffectCommands { + 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 +245,12 @@ public class EffectCommands { + 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++; + } + } +diff --git a/net/minecraft/server/commands/EnchantCommand.java b/net/minecraft/server/commands/EnchantCommand.java +index fe86823f1a02d66df143756f00ee56fb9f634475..b62ed9d5456ae2c050c4d502b10c5e50c7265b96 100644 +--- a/net/minecraft/server/commands/EnchantCommand.java ++++ b/net/minecraft/server/commands/EnchantCommand.java +@@ -68,51 +68,78 @@ public class EnchantCommand { + ); + } + ++ // 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 targets, Holder 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 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, final int level, final java.util.concurrent.atomic.AtomicReference 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 + } +diff --git a/net/minecraft/server/commands/ExperienceCommand.java b/net/minecraft/server/commands/ExperienceCommand.java +index cb59af8018d3009876a47fae249885c00b6c7b57..e0d95f61e8a2841979bc9b5381dfaf7d3239beb7 100644 +--- a/net/minecraft/server/commands/ExperienceCommand.java ++++ b/net/minecraft/server/commands/ExperienceCommand.java +@@ -131,14 +131,18 @@ public class ExperienceCommand { + } + + 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 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 +161,11 @@ public class ExperienceCommand { + 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) { +diff --git a/net/minecraft/server/commands/FillBiomeCommand.java b/net/minecraft/server/commands/FillBiomeCommand.java +index bb2c8612b27bb04758c467ec6245de1236fc4de1..d5ae0eeb504b9306015de37abc59bf1a76a23837 100644 +--- a/net/minecraft/server/commands/FillBiomeCommand.java ++++ b/net/minecraft/server/commands/FillBiomeCommand.java +@@ -107,6 +107,16 @@ public class FillBiomeCommand { + return fill(level, from, to, biome, biome1 -> true, message -> {}); + } + ++ // Folia start - region threading ++ private static void sendMessage(Consumer> src, Supplier> supplier) { ++ Either 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 fill( + ServerLevel level, BlockPos from, BlockPos to, Holder biome, Predicate> filter, Consumer> messageOutput + ) { +@@ -118,6 +128,17 @@ public class FillBiomeCommand { + 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 list = new ArrayList<>(); + + for (int sectionPosMinZ = SectionPos.blockToSectionCoord(boundingBox.minZ()); +@@ -158,6 +179,11 @@ public class FillBiomeCommand { + ) + ); + return Either.left(mutableInt.getValue()); ++ // Folia start - region threading ++ }); // sendMessage ++ }); // loadChunksASync ++ return Either.left(Integer.valueOf(0)); ++ // Folia end - region threading + } + } + +diff --git a/net/minecraft/server/commands/FillCommand.java b/net/minecraft/server/commands/FillCommand.java +index a224f8cc122fc6d79b4abd08815f58f0e6aa340b..89154adfc659afa188cd771e70087e3b1a9c98b9 100644 +--- a/net/minecraft/server/commands/FillCommand.java ++++ b/net/minecraft/server/commands/FillCommand.java +@@ -151,6 +151,12 @@ public class FillCommand { + ); + } + ++ // 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 replacingPredicate + ) throws CommandSyntaxException { +@@ -161,6 +167,18 @@ public class FillCommand { + } else { + List 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 +205,13 @@ public class FillCommand { + } 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 + } + } + +diff --git a/net/minecraft/server/commands/ForceLoadCommand.java b/net/minecraft/server/commands/ForceLoadCommand.java +index 619ffb7846047d3e033378c750dc4ceaf9ac6239..6e174d54a3bf6a7a23a0aa6e7802b407e3969a47 100644 +--- a/net/minecraft/server/commands/ForceLoadCommand.java ++++ b/net/minecraft/server/commands/ForceLoadCommand.java +@@ -97,7 +97,17 @@ public class ForceLoadCommand { + ); + } + ++ // 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 resourceKey = level.dimension(); +@@ -109,14 +119,22 @@ public class ForceLoadCommand { + ), + 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 resourceKey = level.dimension(); + LongSet forcedChunks = level.getForcedChunks(); + int size = forcedChunks.size(); +@@ -134,20 +152,27 @@ public class ForceLoadCommand { + } 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 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 +232,18 @@ public class ForceLoadCommand { + ); + } + +- 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 + } + } +diff --git a/net/minecraft/server/commands/GameModeCommand.java b/net/minecraft/server/commands/GameModeCommand.java +index c44cdbbdc06b25bd20a208386545a10af9b96df8..f6204e765afda2668ab394c570444fbb7f152b8b 100644 +--- a/net/minecraft/server/commands/GameModeCommand.java ++++ b/net/minecraft/server/commands/GameModeCommand.java +@@ -54,15 +54,18 @@ public class GameModeCommand { + 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; +diff --git a/net/minecraft/server/commands/GiveCommand.java b/net/minecraft/server/commands/GiveCommand.java +index 8b7af734ca4ed3cafa810460b2cea6c1e6342a69..6f5d88d83ad724fa2b7549075b687aebd4b24eed 100644 +--- a/net/minecraft/server/commands/GiveCommand.java ++++ b/net/minecraft/server/commands/GiveCommand.java +@@ -65,32 +65,34 @@ public class GiveCommand { + 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 + } + } + +diff --git a/net/minecraft/server/commands/KillCommand.java b/net/minecraft/server/commands/KillCommand.java +index e8ab673921c8089a35a2e678d7a6efed1f728cd7..287681a351f49eabd4f480396314a882bee73645 100644 +--- a/net/minecraft/server/commands/KillCommand.java ++++ b/net/minecraft/server/commands/KillCommand.java +@@ -24,7 +24,9 @@ public class KillCommand { + + private static int kill(CommandSourceStack source, Collection 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) { +diff --git a/net/minecraft/server/commands/PlaceCommand.java b/net/minecraft/server/commands/PlaceCommand.java +index f019285714cf6e7ac08d6b3b96fe705b8a564c28..4decfa02f0fa11a14abd48944e9cb2dd86bb96a2 100644 +--- a/net/minecraft/server/commands/PlaceCommand.java ++++ b/net/minecraft/server/commands/PlaceCommand.java +@@ -233,36 +233,79 @@ public class PlaceCommand { + ); + } + ++ // 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> 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 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, 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 +348,29 @@ public class PlaceCommand { + ); + 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 optional; +@@ -340,9 +398,17 @@ public class PlaceCommand { + () -> 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 { +diff --git a/net/minecraft/server/commands/RecipeCommand.java b/net/minecraft/server/commands/RecipeCommand.java +index d171a5b8c1969f6a482f029afa5fb0228aefb04d..c8ab7f56c5e5af99b5410784a3ae33dedd7bf2f3 100644 +--- a/net/minecraft/server/commands/RecipeCommand.java ++++ b/net/minecraft/server/commands/RecipeCommand.java +@@ -81,7 +81,12 @@ public class RecipeCommand { + 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 +108,12 @@ public class RecipeCommand { + 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) { +diff --git a/net/minecraft/server/commands/SetBlockCommand.java b/net/minecraft/server/commands/SetBlockCommand.java +index 8b72116b80da0497e255ce5a3f3c7bccb6321aec..05b824409546ba8bacf7efdaeac106af89ff0715 100644 +--- a/net/minecraft/server/commands/SetBlockCommand.java ++++ b/net/minecraft/server/commands/SetBlockCommand.java +@@ -80,10 +80,21 @@ public class SetBlockCommand { + ); + } + ++ // 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 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 +113,16 @@ public class SetBlockCommand { + } 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 { +diff --git a/net/minecraft/server/commands/SetSpawnCommand.java b/net/minecraft/server/commands/SetSpawnCommand.java +index e38c7f012098e46337561b2225b31a7097495647..6fc6a748a8096524440d32d692088a8176875786 100644 +--- a/net/minecraft/server/commands/SetSpawnCommand.java ++++ b/net/minecraft/server/commands/SetSpawnCommand.java +@@ -69,7 +69,11 @@ public class SetSpawnCommand { + final Collection 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 +diff --git a/net/minecraft/server/commands/SummonCommand.java b/net/minecraft/server/commands/SummonCommand.java +index b68c0e617d3593cc9ba999ed25ea2c1b7c762597..2d4bf39f3f35811a7f48f361c91ee3d5722ba839 100644 +--- a/net/minecraft/server/commands/SummonCommand.java ++++ b/net/minecraft/server/commands/SummonCommand.java +@@ -88,12 +88,18 @@ public class SummonCommand { + 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; +diff --git a/net/minecraft/server/commands/TeleportCommand.java b/net/minecraft/server/commands/TeleportCommand.java +index 01f8e2fec232210c9311565197860cf0257081fd..174122905addbc88e818cd4946e831aec051b91a 100644 +--- a/net/minecraft/server/commands/TeleportCommand.java ++++ b/net/minecraft/server/commands/TeleportCommand.java +@@ -154,18 +154,7 @@ public class TeleportCommand { + + private static int teleportToEntity(CommandSourceStack source, Collection 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 +279,24 @@ public class TeleportCommand { + 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) { +diff --git a/net/minecraft/server/commands/TimeCommand.java b/net/minecraft/server/commands/TimeCommand.java +index e952ca088a2f36fc7f1eef4d9b217351569becc1..5d1fc3bb00abd177325a292f55d2cf1cddd3158b 100644 +--- a/net/minecraft/server/commands/TimeCommand.java ++++ b/net/minecraft/server/commands/TimeCommand.java +@@ -56,6 +56,7 @@ public class TimeCommand { + } + + 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 +70,12 @@ public class TimeCommand { + + 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 +89,7 @@ public class TimeCommand { + 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 + } + } +diff --git a/net/minecraft/server/commands/WeatherCommand.java b/net/minecraft/server/commands/WeatherCommand.java +index 9b14b6218b2673e9b13b749b566e3b8a6a8d9c7d..dade5adec00c081cb4def7464f0f04d2f5a6ae26 100644 +--- a/net/minecraft/server/commands/WeatherCommand.java ++++ b/net/minecraft/server/commands/WeatherCommand.java +@@ -48,20 +48,26 @@ public class WeatherCommand { + } + + 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; + } + } +diff --git a/net/minecraft/server/commands/WorldBorderCommand.java b/net/minecraft/server/commands/WorldBorderCommand.java +index e2697b03a0d204eea537e3aaec2dd8fb9f426722..f6af541a7076c3fefb237b865038d08919de35ed 100644 +--- a/net/minecraft/server/commands/WorldBorderCommand.java ++++ b/net/minecraft/server/commands/WorldBorderCommand.java +@@ -134,18 +134,39 @@ public class WorldBorderCommand { + ); + } + ++ // 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 +175,79 @@ public class WorldBorderCommand { + 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 +259,24 @@ public class WorldBorderCommand { + ), + 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 +306,14 @@ public class WorldBorderCommand { + 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 + } + } +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 97a294d2f5c1ddf0af7ffec3e1425eb329c5751b..341e400f789e0eda29827e2c45c483a470d2e982 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -425,7 +425,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + @Override + public void tickConnection() { + super.tickConnection(); +- this.handleConsoleInputs(); ++ // Folia - region threading + } + + @Override +@@ -732,7 +732,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + public String runCommand(RconConsoleSource rconConsoleSource, String s) { + rconConsoleSource.prepareForCommand(); +- this.executeBlocking(() -> { ++ final java.util.concurrent.atomic.AtomicReference 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 +742,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + 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 + } +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index b3f498558614243cf633dcd71e3c49c2c55e6e0f..329e57af5cbd38425e80dba96eb972fdfb0ce5ce 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -128,8 +128,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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 entityMap = new Int2ObjectOpenHashMap<>(); ++ //private final PlayerMap playerMap = new PlayerMap(); // Folia - region threading ++ //public final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); // Folia - region threading + private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); + // Paper - rewrite chunk system + public int serverViewDistance; +@@ -797,12 +797,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + 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 +810,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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 +830,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + 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 +866,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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 +879,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot + if (i != 0) { + int updateInterval = type.updateInterval(); +- if (this.entityMap.containsKey(entity.getId())) { ++ if (entity.moonrise$getTrackedEntity() != null) { // Folia - region threading + throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Entity is already tracked!")); + } else { + ChunkMap.TrackedEntity trackedEntity = new ChunkMap.TrackedEntity(entity, i, updateInterval, type.trackDeltas()); +- this.entityMap.put(entity.getId(), trackedEntity); ++ //this.entityMap.put(entity.getId(), trackedEntity); // Folia - region threading + // Paper start - optimise entity tracker + if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$getTrackedEntity() != null) { + throw new IllegalStateException("Entity is already tracked"); + } + ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(trackedEntity); + // Paper end - optimise entity tracker +- trackedEntity.updatePlayers(this.level.players()); ++ trackedEntity.updatePlayers(this.level.getLocalPlayers()); // Folia - region threading + if (entity instanceof ServerPlayer serverPlayer) { + this.updatePlayerStatus(serverPlayer, true); + +- for (ChunkMap.TrackedEntity trackedEntity1 : this.entityMap.values()) { ++ // Folia start - region threading ++ for (Entity possible : this.level.getCurrentWorldData().trackerEntities) { ++ ChunkMap.TrackedEntity trackedEntity1 = possible.moonrise$getTrackedEntity(); ++ if (trackedEntity1 == null) { ++ continue; ++ } ++ // Folia end - region threading + if (trackedEntity1.entity != serverPlayer) { + trackedEntity1.updatePlayer(serverPlayer); + } +@@ -924,12 +916,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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 +937,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + // Paper start - optimise entity tracker + private void newTrackerTick() { ++ final io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.level.getCurrentWorldData(); // Folia - region threading + final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup)((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();; ++ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = this.level.moonrise$getNearbyPlayers(); // Folia - region threading + +- final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = entityLookup.trackerEntities; ++ final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = worldData.trackerEntities; // Folia - region threading + final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); + for (int i = 0, len = trackerEntities.size(); i < len; ++i) { + final Entity entity = trackerEntitiesRaw[i]; +@@ -948,7 +949,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (tracker == null) { + continue; + } +- ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkData().nearbyPlayers); ++ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); // Folia - region threading + if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$hasPlayers() + || ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) { + tracker.serverEntity.sendChanges(); +@@ -966,44 +967,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end - optimise entity tracker + // Paper - rewrite chunk system + +- List list = Lists.newArrayList(); +- List 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 +1206,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + 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 +diff --git a/net/minecraft/server/level/DistanceManager.java b/net/minecraft/server/level/DistanceManager.java +index 5eab6179ce3913cb4e4d424f910ba423faf21c85..338f9d047101619605cedab172358b4fd737af97 100644 +--- a/net/minecraft/server/level/DistanceManager.java ++++ b/net/minecraft/server/level/DistanceManager.java +@@ -57,16 +57,16 @@ public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches + } + // Paper end - rewrite chunk system + // Paper start - chunk tick iteration optimisation +- private final ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap 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 +74,9 @@ public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches + 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 +208,15 @@ public abstract class DistanceManager implements ca.spottedleaf.moonrise.patches + } + + 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() { +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index 6540b2d6a1062d883811ce240c49d30d1925b291..548f5f0382c81ca86d238bfd7f94008bbd6e41bc 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -61,18 +61,14 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + 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 tickingChunks = new ArrayList<>(); +- private final Set chunkHoldersToBroadcast = new ReferenceOpenHashSet<>(); +- @Nullable +- @VisibleForDebug +- private NaturalSpawner.SpawnState lastSpawnState; ++ // Folia - moved to regionised world data + // Paper start + private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); + public int getFullChunksCount() { +@@ -98,6 +94,11 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + 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 completable = new CompletableFuture<>(); + chunkTaskScheduler.scheduleChunkLoad( +@@ -154,9 +155,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + // Paper start - chunk tick iteration optimisations + private final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom shuffleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(0L); + private boolean isChunkNearPlayer(final ChunkMap chunkMap, final ChunkPos chunkPos, final LevelChunk levelChunk) { +- final ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData chunkData = ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)levelChunk).moonrise$getChunkAndHolder().holder()) +- .moonrise$getRealChunkHolder().holderData; +- final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk nearbyPlayers = chunkData.nearbyPlayers; ++ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk nearbyPlayers = this.level.moonrise$getNearbyPlayers().getChunk(chunkPos); // Folia - region threading + if (nearbyPlayers == null) { + return false; + } +@@ -355,6 +354,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + public CompletableFuture> getChunkFuture(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) { ++ if (true) throw new UnsupportedOperationException(); // Folia - region threading + boolean flag = Thread.currentThread() == this.mainThread; + CompletableFuture> chunkFutureMainThread; + if (flag) { +@@ -502,14 +502,15 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + 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 list = this.tickingChunks; ++ List list = regionizedWorldData.temporaryChunkTickList; // Folia - region threading + + try { + profilerFiller.push("filteringTickingChunks"); +@@ -532,23 +533,24 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + 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 output) { + // Paper start - chunk tick iteration optimisation + final ca.spottedleaf.moonrise.common.list.ReferenceList 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 +571,14 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + private void tickChunks(ProfilerFiller profiler, long timeInhabited, List 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 +591,26 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + // 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 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 +676,23 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + 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 void addRegionTicket(TicketType type, ChunkPos pos, int distance, T value) { +@@ -766,7 +774,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + @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 +784,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + + public void onChunkReadyToSend(ChunkHolder chunkHolder) { + if (chunkHolder.hasChangesToBroadcast()) { +- this.chunkHoldersToBroadcast.add(chunkHolder); ++ throw new UnsupportedOperationException(); // Folia - region threading + } + } + +@@ -812,20 +821,76 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + return ServerChunkCache.this.mainThread; + } + ++ // Folia start - region threading ++ @Override ++ public CompletableFuture submit(Supplier task) { ++ if (true) { ++ throw new UnsupportedOperationException(); ++ } ++ return super.submit(task); ++ } ++ ++ @Override ++ public CompletableFuture 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 + } +diff --git a/net/minecraft/server/level/ServerEntityGetter.java b/net/minecraft/server/level/ServerEntityGetter.java +index 794770985c261fd56806188237921b5ec5e548e6..b715d1fbde9db81a2515249bb9a0fc7a5fee40f0 100644 +--- a/net/minecraft/server/level/ServerEntityGetter.java ++++ b/net/minecraft/server/level/ServerEntityGetter.java +@@ -14,17 +14,17 @@ public interface ServerEntityGetter extends EntityGetter { + + @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 +57,7 @@ public interface ServerEntityGetter extends EntityGetter { + default List getNearbyPlayers(TargetingConditions targetingConditions, LivingEntity source, AABB area) { + List 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); + } +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 67de67cf1e33672fe33fbb88aeb92e3a020a4ae5..ce310e2dc1bb46e17143bffc5e6cec7d43a0389d 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -179,42 +179,40 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private static final Logger LOGGER = LogUtils.getLogger(); + private static final int EMPTY_TIME_NO_TICK = 300; + private static final int MAX_SCHEDULED_TICKS_PER_TICK = 65536; +- final List players = Lists.newArrayList(); ++ final List players = new java.util.concurrent.CopyOnWriteArrayList<>(); // Folia - region threading + public final ServerChunkCache chunkSource; + private final MinecraftServer server; + public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type + private int lastSpawnChunkRadius; +- final EntityTickList entityTickList = new EntityTickList(); ++ //final EntityTickList entityTickList = new EntityTickList(); // Folia - region threading + // Paper - rewrite chunk system + private final GameEventDispatcher gameEventDispatcher; + public boolean noSave; + private final SleepStatus sleepStatus; + private int emptyTime; + private final PortalForcer portalForcer; +- private final LevelTicks blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); +- private final LevelTicks fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); +- private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); +- final Set navigatingMobs = new ObjectOpenHashSet<>(); ++ //private final LevelTicks blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); // Folia - region threading ++ //private final LevelTicks fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); // Folia - region threading ++ //private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); // Folia - region threading ++ //final Set navigatingMobs = new ObjectOpenHashSet<>(); // Folia - region threading + volatile boolean isUpdatingNavigations; + protected final Raids raids; +- private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); +- private final List blockEventsToReschedule = new ArrayList<>(64); +- private boolean handlingTick; ++ //private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); // Folia - region threading ++ //private final List blockEventsToReschedule = new ArrayList<>(64); // Folia - region threading ++ //private boolean handlingTick; // Folia - region threading + private final List customSpawners; + @Nullable + private EndDragonFight dragonFight; +- final Int2ObjectMap dragonParts = new Int2ObjectOpenHashMap<>(); ++ final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable dragonParts = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); // Folia - region threading + private final StructureManager structureManager; + private final StructureCheck structureCheck; +- private final boolean tickTime; ++ public final boolean tickTime; // Folia - region threading + private final RandomSequences randomSequences; + + // CraftBukkit start + public final LevelStorageSource.LevelStorageAccess levelStorageAccess; + public final UUID uuid; +- public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent +- public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent +- private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) ++ // Folia - region threading - move to regionised world data + + public LevelChunk getChunkIfLoaded(int x, int z) { + return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately +@@ -242,6 +240,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + int minChunkZ = minBlockZ >> 4; + int maxChunkZ = maxBlockZ >> 4; + ++ // Folia start - region threading ++ // don't let players move into regions not owned ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, minChunkX, minChunkZ, maxChunkX, maxChunkZ)) { ++ return false; ++ } ++ // Folia end - region threading ++ + ServerChunkCache chunkProvider = this.getChunkSource(); + + for (int cx = minChunkX; cx <= maxChunkX; ++cx) { +@@ -297,11 +302,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler; + private long lastMidTickFailure; + private long tickedBlocksOrFluids; +- private final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = new ca.spottedleaf.moonrise.common.misc.NearbyPlayers((ServerLevel)(Object)this); +- private static final ServerChunkCache.ChunkAndHolder[] EMPTY_CHUNK_AND_HOLDERS = new ServerChunkCache.ChunkAndHolder[0]; +- private final ca.spottedleaf.moonrise.common.list.ReferenceList loadedChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); +- private final ca.spottedleaf.moonrise.common.list.ReferenceList tickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); +- private final ca.spottedleaf.moonrise.common.list.ReferenceList entityTickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); ++ // Folia - region threading - move to regionized data + + @Override + public final LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) { +@@ -359,7 +360,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public final int moonrise$getRegionChunkShift() { +- return io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift(); ++ return this.regioniser.sectionChunkShift; // Folia - region threading + } + + @Override +@@ -460,22 +461,22 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public final ca.spottedleaf.moonrise.common.misc.NearbyPlayers moonrise$getNearbyPlayers() { +- return this.nearbyPlayers; ++ return this.getCurrentWorldData().getNearbyPlayers(); // Folia - region threading + } + + @Override + public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getLoadedChunks() { +- return this.loadedChunks; ++ return this.getCurrentWorldData().getChunks(); // Folia - region threading + } + + @Override + public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getTickingChunks() { +- return this.tickingChunks; ++ return this.getCurrentWorldData().getTickingChunks(); // Folia - region threading + } + + @Override + public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getEntityTickingChunks() { +- return this.entityTickingChunks; ++ return this.getCurrentWorldData().getEntityTickingChunks(); // Folia - region threading + } + + @Override +@@ -495,80 +496,85 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - rewrite chunk system + // Paper start - chunk tick iteration + private static final ServerChunkCache.ChunkAndHolder[] EMPTY_PLAYER_CHUNK_HOLDERS = new ServerChunkCache.ChunkAndHolder[0]; +- private final ca.spottedleaf.moonrise.common.list.ReferenceList playerTickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_PLAYER_CHUNK_HOLDERS); +- private final it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap playerTickingRequests = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); ++ // Folia - region threading + + @Override + public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getPlayerTickingChunks() { +- return this.playerTickingChunks; ++ throw new UnsupportedOperationException(); // Folia - region threading + } + + @Override + public final void moonrise$markChunkForPlayerTicking(final LevelChunk chunk) { +- final ChunkPos pos = chunk.getPos(); +- if (!this.playerTickingRequests.containsKey(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos))) { +- return; +- } +- +- this.playerTickingChunks.add(((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()); ++ // Folia - region threading + } + + @Override + public final void moonrise$removeChunkForPlayerTicking(final LevelChunk chunk) { +- this.playerTickingChunks.remove(((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()); ++ // Folia - region threading + } + + @Override + public final void moonrise$addPlayerTickingRequest(final int chunkX, final int chunkZ) { +- ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot add ticking request async"); +- +- final long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ); +- +- if (this.playerTickingRequests.addTo(chunkKey, 1) != 0) { +- // already added +- return; +- } +- +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler() +- .chunkHolderManager.getChunkHolder(chunkKey); +- +- if (chunkHolder == null || !chunkHolder.isTickingReady()) { +- return; +- } +- +- this.playerTickingChunks.add( +- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder() +- ); ++ // Folia - region threading + } + + @Override + public final void moonrise$removePlayerTickingRequest(final int chunkX, final int chunkZ) { +- ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot remove ticking request async"); ++ // Folia - region threading ++ } ++ // Paper end - chunk tick iteration ++ // Folia start - region threading ++ public final io.papermc.paper.threadedregions.TickRegions tickRegions = new io.papermc.paper.threadedregions.TickRegions(); ++ public final io.papermc.paper.threadedregions.ThreadedRegionizer regioniser; ++ { ++ this.regioniser = new io.papermc.paper.threadedregions.ThreadedRegionizer<>( ++ (int)Math.max(1L, (8L * 16L * 16L) / (1L << (2 * (io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift())))), ++ (1.0 / 6.0), ++ Math.max(1, 8 / (1 << io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift())), ++ 1, ++ io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift(), ++ this, ++ this.tickRegions ++ ); ++ } ++ public final io.papermc.paper.threadedregions.RegionizedTaskQueue.WorldRegionTaskData taskQueueRegionData = new io.papermc.paper.threadedregions.RegionizedTaskQueue.WorldRegionTaskData(this); ++ public static final int WORLD_INIT_NOT_CHECKED = 0; ++ public static final int WORLD_INIT_CHECKING = 1; ++ public static final int WORLD_INIT_CHECKED = 2; ++ public final java.util.concurrent.atomic.AtomicInteger checkInitialised = new java.util.concurrent.atomic.AtomicInteger(WORLD_INIT_NOT_CHECKED); ++ public ChunkPos randomSpawnSelection; + +- final long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ); +- final int val = this.playerTickingRequests.addTo(chunkKey, -1); ++ public static final record PendingTeleport(Entity.EntityTreeNode rootVehicle, Vec3 to) {} ++ private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet pendingTeleports = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); + +- if (val <= 0) { +- throw new IllegalStateException("Negative counter"); ++ public void pushPendingTeleport(final PendingTeleport teleport) { ++ synchronized (this.pendingTeleports) { ++ this.pendingTeleports.add(teleport); + } ++ } + +- if (val != 1) { +- // still has at least one request +- return; ++ public boolean removePendingTeleport(final PendingTeleport teleport) { ++ synchronized (this.pendingTeleports) { ++ return this.pendingTeleports.remove(teleport); + } ++ } + +- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler() +- .chunkHolderManager.getChunkHolder(chunkKey); ++ public List removeAllRegionTeleports() { ++ final List ret = new ArrayList<>(); + +- if (chunkHolder == null || !chunkHolder.isTickingReady()) { +- return; ++ synchronized (this.pendingTeleports) { ++ for (final java.util.Iterator iterator = this.pendingTeleports.iterator(); iterator.hasNext(); ) { ++ final PendingTeleport pendingTeleport = iterator.next(); ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, pendingTeleport.to())) { ++ ret.add(pendingTeleport); ++ iterator.remove(); ++ } ++ } + } + +- this.playerTickingChunks.remove( +- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder() +- ); ++ return ret; + } +- // Paper end - chunk tick iteration ++ // Folia end - region threading + + public ServerLevel( + MinecraftServer server, +@@ -633,7 +639,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ); + this.chunkSource.getGeneratorState().ensureStructuresGenerated(); + this.portalForcer = new PortalForcer(this); +- this.updateSkyBrightness(); ++ //this.updateSkyBrightness(); // Folia - region threading - delay until first tick + this.prepareWeather(); + this.getWorldBorder().setAbsoluteMaxSize(server.getAbsoluteMaxWorldSize()); + this.raids = this.getDataStorage().computeIfAbsent(Raids.factory(this), Raids.getFileId(this.dimensionTypeRegistration())); +@@ -681,7 +687,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); + // Paper end - rewrite chunk system + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit ++ this.updateTickData(); // Folia - region threading - make sure it is initialised before ticked ++ } ++ ++ // Folia start - region threading ++ public void updateTickData() { ++ this.tickData = new io.papermc.paper.threadedregions.RegionizedServer.WorldLevelData(this, this.serverLevelData.getGameTime(), this.serverLevelData.getDayTime()); + } ++ // Folia end - region threading + + // Paper start + @Override +@@ -709,61 +722,39 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return this.getChunkSource().getGenerator().getBiomeSource().getNoiseBiome(x, y, z, this.getChunkSource().randomState().sampler()); + } + ++ @Override // Folia - region threading + public StructureManager structureManager() { + return this.structureManager; + } + +- public void tick(BooleanSupplier hasTimeLeft) { ++ public void tick(BooleanSupplier hasTimeLeft, io.papermc.paper.threadedregions.TickRegions.TickRegionData region) { // Folia - regionised ticking ++ final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - regionised ticking + ProfilerFiller profilerFiller = Profiler.get(); +- this.handlingTick = true; ++ regionizedWorldData.setHandlingTick(true); // Folia - regionised ticking + TickRateManager tickRateManager = this.tickRateManager(); + boolean runsNormally = tickRateManager.runsNormally(); + if (runsNormally) { + profilerFiller.push("world border"); +- this.getWorldBorder().tick(); ++ //this.getWorldBorder().tick(); // Folia - regionised ticking + profilerFiller.popPush("weather"); +- this.advanceWeatherCycle(); ++ //this.advanceWeatherCycle(); // Folia - regionised ticking + profilerFiller.pop(); + } + +- int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); +- if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { +- // Paper start - create time skip event - move up calculations +- final long newDayTime = this.levelData.getDayTime() + 24000L; +- org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent( +- this.getWorld(), +- org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP, +- (newDayTime - newDayTime % 24000L) - this.getDayTime() +- ); +- // Paper end - create time skip event - move up calculations +- if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { +- // Paper start - call time skip event if gamerule is enabled +- // long l = this.levelData.getDayTime() + 24000L; // Paper - diff on change to above - newDayTime +- // this.setDayTime(l - l % 24000L); // Paper - diff on change to above - event param +- if (event.callEvent()) { +- this.setDayTime(this.getDayTime() + event.getSkipAmount()); +- } +- // Paper end - call time skip event if gamerule is enabled +- } +- +- if (!event.isCancelled()) this.wakeUpAllPlayers(); // Paper - only wake up players if time skip event is not cancelled +- if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) { +- this.resetWeatherCycle(); +- } +- } ++ this.tickSleep(); // Folia - region threading - move into tickSleep + +- this.updateSkyBrightness(); ++ //this.updateSkyBrightness(); // Folia - region threading + if (runsNormally) { + this.tickTime(); + } + + profilerFiller.push("tickPending"); + if (!this.isDebug() && runsNormally) { +- long l = this.getGameTime(); ++ long l = regionizedWorldData.getRedstoneGameTime(); // Folia - region threading + profilerFiller.push("blockTicks"); +- this.blockTicks.tick(l, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks ++ regionizedWorldData.getBlockLevelTicks().tick(l, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks // Folia - region ticking + profilerFiller.popPush("fluidTicks"); +- this.fluidTicks.tick(l, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks ++ regionizedWorldData.getFluidLevelTicks().tick(l, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks // Folia - region ticking + profilerFiller.pop(); + } + +@@ -779,9 +770,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.runBlockEvents(); + } + +- this.handlingTick = false; ++ regionizedWorldData.setHandlingTick(false); // Folia - regionised ticking + profilerFiller.pop(); +- boolean flag = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this ++ boolean flag = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this // Folia - unrestore this, we always need to tick empty worlds + if (flag) { + this.resetEmptyTime(); + } +@@ -789,19 +780,29 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + if (flag || this.emptyTime++ < 300) { + profilerFiller.push("entities"); + if (this.dragonFight != null && runsNormally) { ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, this.dragonFight.origin)) { // Folia - region threading + profilerFiller.push("dragonFight"); + this.dragonFight.tick(); + profilerFiller.pop(); ++ } else { // Folia start - region threading ++ // try to load dragon fight ++ ChunkPos fightCenter = new ChunkPos(this.dragonFight.origin); ++ this.chunkSource.addTicketAtLevel( ++ TicketType.UNKNOWN, fightCenter, ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ fightCenter ++ ); ++ } // Folia end - region threading + } + + io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR +- this.entityTickList +- .forEach( ++ regionizedWorldData // Folia - regionised ticking ++ .forEachTickingEntity( // Folia - regionised ticking + entity -> { + if (!entity.isRemoved()) { + if (!tickRateManager.isEntityFrozen(entity)) { + profilerFiller.push("checkDespawn"); + entity.checkDespawn(); ++ if (entity.isRemoved()) return; // Folia - region threading - if we despawned, DON'T TICK IT! + profilerFiller.pop(); + if (true) { // Paper - rewrite chunk system + Entity vehicle = entity.getVehicle(); +@@ -830,6 +831,36 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + profilerFiller.pop(); + } + ++ // Folia start - region threading ++ public void tickSleep() { ++ int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); ++ if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { ++ // Paper start - create time skip event - move up calculations ++ final long newDayTime = this.levelData.getDayTime() + 24000L; ++ org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent( ++ this.getWorld(), ++ org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP, ++ (newDayTime - newDayTime % 24000L) - this.getDayTime() ++ ); ++ // Paper end - create time skip event - move up calculations ++ if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { ++ // Paper start - call time skip event if gamerule is enabled ++ // long l = this.levelData.getDayTime() + 24000L; // Paper - diff on change to above - newDayTime ++ // this.setDayTime(l - l % 24000L); // Paper - diff on change to above - event param ++ if (event.callEvent()) { ++ this.setDayTime(this.getDayTime() + event.getSkipAmount()); ++ } ++ // Paper end - call time skip event if gamerule is enabled ++ } ++ ++ if (!event.isCancelled()) this.wakeUpAllPlayers(); // Paper - only wake up players if time skip event is not cancelled ++ if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) { ++ this.resetWeatherCycle(); ++ } ++ } ++ } ++ // Folia end - region threading ++ + @Override + public boolean shouldTickBlocksAt(long chunkPos) { + // Paper start - rewrite chunk system +@@ -840,12 +871,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + protected void tickTime() { + if (this.tickTime) { +- long l = this.levelData.getGameTime() + 1L; +- this.serverLevelData.setGameTime(l); ++ io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - region threading ++ long l = regionizedWorldData.getRedstoneGameTime() + 1L; // Folia - region threading ++ regionizedWorldData.setRedstoneGameTime(l); // Folia - region threading + Profiler.get().push("scheduledFunctions"); +- this.serverLevelData.getScheduledEvents().tick(this.server, l); ++ //this.serverLevelData.getScheduledEvents().tick(this.server, l); // Folia - region threading - TODO any way to bring this in? + Profiler.get().pop(); +- if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { ++ if (false && this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { // Folia - region threading + this.setDayTime(this.levelData.getDayTime() + 1L); + } + } +@@ -863,16 +895,27 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + private void wakeUpAllPlayers() { + this.sleepStatus.removeAllSleepers(); +- this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false)); ++ // Folia start - region threading ++ this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach((ServerPlayer entityplayer) -> { ++ // Folia start - region threading ++ entityplayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { ++ if (player.level() != ServerLevel.this || !player.isSleeping()) { ++ return; ++ } ++ player.stopSleepInBed(false, false); ++ }, null, 1L); ++ } ++ ); ++ // Folia end - region threading + } + + // Paper start - optimise random ticking +- private final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); ++ private final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource.INSTANCE; // Folia - region threading + + private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) { + final LevelChunkSection[] sections = chunk.getSections(); + final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this); +- final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom; ++ final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = this.simpleRandom; // Folia - region threading + final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); + + final ChunkPos cpos = chunk.getPos(); +@@ -919,7 +962,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - optimise random ticking + + public void tickChunk(LevelChunk chunk, int randomTickSpeed) { +- final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom; // Paper - optimise random ticking ++ final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Folia - region threading + ChunkPos pos = chunk.getPos(); + boolean isRaining = this.isRaining(); + int minBlockX = pos.getMinBlockX(); +@@ -1044,7 +1087,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + public boolean isHandlingTick() { +- return this.handlingTick; ++ return this.getCurrentWorldData().isHandlingTick(); // Folia - regionised ticking + } + + public boolean canSleepThroughNights() { +@@ -1070,6 +1113,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + public void updateSleepingPlayerList() { ++ // Folia start - region threading ++ if (!io.papermc.paper.threadedregions.RegionizedServer.isGlobalTickThread()) { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { ++ ServerLevel.this.updateSleepingPlayerList(); ++ }); ++ return; ++ } ++ // Folia end - region threading + if (!this.players.isEmpty() && this.sleepStatus.update(this.players)) { + this.announceSleepStatus(); + } +@@ -1080,7 +1131,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return this.server.getScoreboard(); + } + +- private void advanceWeatherCycle() { ++ public void advanceWeatherCycle() { // Folia - region threading - public + boolean isRaining = this.isRaining(); + if (this.dimensionType().hasSkyLight()) { + if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE)) { +@@ -1166,7 +1217,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel)); + } + */ +- for (ServerPlayer player : this.players) { ++ ServerPlayer[] players = this.players.toArray(new ServerPlayer[0]); // Folia - region threading ++ for (ServerPlayer player : players) { // Folia - region threading + if (player.level() == this) { + player.tickWeather(); + } +@@ -1174,13 +1226,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + if (isRaining != this.isRaining()) { + // Only send weather packets to those affected +- for (ServerPlayer player : this.players) { ++ for (ServerPlayer player : players) { // Folia - region threading + if (player.level() == this) { + player.setPlayerWeather((!isRaining ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR), false); + } + } + } +- for (ServerPlayer player : this.players) { ++ for (ServerPlayer player : players) { // Folia - region threading + if (player.level() == this) { + player.updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel); + } +@@ -1241,13 +1293,10 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + // Paper start - log detailed entity tick information + // TODO replace with varhandle +- static final java.util.concurrent.atomic.AtomicReference currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>(); ++ // Folia - region threading + + public static List getCurrentlyTickingEntities() { +- Entity ticking = currentlyTickingEntity.get(); +- List ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking }); +- +- return ret; ++ throw new UnsupportedOperationException(); // Folia - region threading + } + // Paper end - log detailed entity tick information + +@@ -1255,9 +1304,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper start - log detailed entity tick information + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); + try { +- if (currentlyTickingEntity.get() == null) { +- currentlyTickingEntity.lazySet(entity); +- } ++ // Folia - region threading + // Paper end - log detailed entity tick information + entity.setOldPosAndRot(); + ProfilerFiller profilerFiller = Profiler.get(); +@@ -1267,7 +1314,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + final boolean isActive = io.papermc.paper.entity.activation.ActivationRange.checkIfActive(entity); // Paper - EAR 2 + if (isActive) { // Paper - EAR 2 + entity.tick(); +- entity.postTick(); // CraftBukkit ++ // Folia start - region threading ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) { ++ // removed from region while ticking ++ return; ++ } ++ if (entity.handlePortal()) { ++ // portalled ++ return; ++ } ++ // Folia end - region threading + } else {entity.inactiveTick();} // Paper - EAR 2 + profilerFiller.pop(); + +@@ -1276,9 +1332,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + // Paper start - log detailed entity tick information + } finally { +- if (currentlyTickingEntity.get() == entity) { +- currentlyTickingEntity.lazySet(null); +- } ++ // Folia - region threading + } + // Paper end - log detailed entity tick information + } +@@ -1286,7 +1340,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2 + if (passengerEntity.isRemoved() || passengerEntity.getVehicle() != ridingEntity) { + passengerEntity.stopRiding(); +- } else if (passengerEntity instanceof Player || this.entityTickList.contains(passengerEntity)) { ++ } else if (passengerEntity instanceof Player || this.getCurrentWorldData().hasEntityTickingEntity(passengerEntity)) { // Folia - region threading + passengerEntity.setOldPosAndRot(); + passengerEntity.tickCount++; + ProfilerFiller profilerFiller = Profiler.get(); +@@ -1295,7 +1349,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper start - EAR 2 + if (isActive) { + passengerEntity.rideTick(); +- passengerEntity.postTick(); // CraftBukkit ++ // Folia start - region threading ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(passengerEntity)) { ++ // removed from region while ticking ++ return; ++ } ++ if (passengerEntity.handlePortal()) { ++ // portalled ++ return; ++ } ++ // Folia end - region threading + } else { + passengerEntity.setDeltaMovement(Vec3.ZERO); + passengerEntity.inactiveTick(); +@@ -1369,19 +1432,20 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + // Paper end - add close param + +- // CraftBukkit start - moved from MinecraftServer.saveChunks +- ServerLevel worldserver1 = this; +- +- this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); +- this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess())); +- this.levelStorageAccess.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); +- // CraftBukkit end ++ // Folia - move into saveLevelData + } + +- private void saveLevelData(boolean join) { ++ public void saveLevelData(boolean join) { // Folia - public + if (this.dragonFight != null) { + this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit + } ++ // Folia start - moved into saveLevelData ++ ServerLevel worldserver1 = this; ++ ++ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); ++ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess())); ++ this.levelStorageAccess.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); ++ // Folia end - moved into saveLevelData + + DimensionDataStorage dataStorage = this.getChunkSource().getDataStorage(); + if (join) { +@@ -1437,6 +1501,19 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return list; + } + ++ // Folia start - region threading ++ @Nullable ++ public ServerPlayer getRandomLocalPlayer() { ++ List list = this.getLocalPlayers(); ++ list = new java.util.ArrayList<>(list); ++ list.removeIf((ServerPlayer player) -> { ++ return !player.isAlive(); ++ }); ++ ++ return list.isEmpty() ? null : (ServerPlayer) list.get(this.random.nextInt(list.size())); ++ } ++ // Folia end - region threading ++ + @Nullable + public ServerPlayer getRandomPlayer() { + List players = this.getPlayers(LivingEntity::isAlive); +@@ -1518,8 +1595,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } else { + if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added + // Paper start - capture all item additions to the world +- if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { +- captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); ++ if (this.getCurrentWorldData().captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { // Folia - region threading ++ this.getCurrentWorldData().captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); // Folia - region threading + return true; + } + // Paper end - capture all item additions to the world +@@ -1694,13 +1771,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { +- if (this.isUpdatingNavigations) { ++ final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - region threading ++ if (false && this.isUpdatingNavigations) { // Folia - region threading + String string = "recursive call to sendBlockUpdated"; + Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); + } + + this.getChunkSource().blockChanged(pos); +- this.pathTypesByPosCache.invalidate(pos); ++ regionizedWorldData.pathTypesByPosCache.invalidate(pos); // Folia - region threading + if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates + VoxelShape collisionShape = oldState.getCollisionShape(this, pos); + VoxelShape collisionShape1 = newState.getCollisionShape(this, pos); +@@ -1708,7 +1786,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + List list = new ObjectArrayList<>(); + + try { // Paper - catch CME see below why +- for (Mob mob : this.navigatingMobs) { ++ for (java.util.Iterator iterator = regionizedWorldData.getNavigatingMobs(); iterator.hasNext();) { // Folia - region threading ++ Mob mob = iterator.next(); // Folia - region threading + PathNavigation navigation = mob.getNavigation(); + if (navigation.shouldRecomputePath(pos)) { + list.add(navigation); +@@ -1725,13 +1804,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - catch CME see below why + + try { +- this.isUpdatingNavigations = true; ++ //this.isUpdatingNavigations = true; // Folia - region threading + + for (PathNavigation pathNavigation : list) { + pathNavigation.recomputePath(); + } + } finally { +- this.isUpdatingNavigations = false; ++ //this.isUpdatingNavigations = false; // Folia - region threading + } + } + } // Paper - option to disable pathfinding updates +@@ -1739,29 +1818,29 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public void updateNeighborsAt(BlockPos pos, Block block) { +- if (captureBlockStates) { return; } // Paper - Cancel all physics during placement ++ if (this.getCurrentWorldData().captureBlockStates) { return; } // Paper - Cancel all physics during placement // Folia - region threading + this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, null, null)); + } + + @Override + public void updateNeighborsAt(BlockPos pos, Block block, @Nullable Orientation orientation) { +- if (captureBlockStates) { return; } // Paper - Cancel all physics during placement +- this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); ++ if (this.getCurrentWorldData().captureBlockStates) { return; } // Paper - Cancel all physics during placement // Folia - region threading ++ this.getCurrentWorldData().neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); // Folia - region threading + } + + @Override + public void updateNeighborsAtExceptFromFacing(BlockPos pos, Block block, Direction facing, @Nullable Orientation orientation) { +- this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, facing, orientation); ++ this.getCurrentWorldData().neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, facing, orientation); // Folia - region threading + } + + @Override + public void neighborChanged(BlockPos pos, Block block, @Nullable Orientation orientation) { +- this.neighborUpdater.neighborChanged(pos, block, orientation); ++ this.getCurrentWorldData().neighborUpdater.neighborChanged(pos, block, orientation); // Folia - region threading + } + + @Override + public void neighborChanged(BlockState state, BlockPos pos, Block block, @Nullable Orientation orientation, boolean movedByPiston) { +- this.neighborUpdater.neighborChanged(state, pos, block, orientation, movedByPiston); ++ this.getCurrentWorldData().neighborUpdater.neighborChanged(state, pos, block, orientation, movedByPiston); // Folia - region threading + } + + @Override +@@ -1851,7 +1930,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // CraftBukkit end + ParticleOptions particleOptions = serverExplosion.isSmall() ? smallExplosionParticles : largeExplosionParticles; + +- for (ServerPlayer serverPlayer : this.players) { ++ for (ServerPlayer serverPlayer : this.getLocalPlayers()) { // Folia - region thraeding + if (serverPlayer.distanceToSqr(vec3) < 4096.0) { + Optional optional = Optional.ofNullable(serverExplosion.getHitPlayers().get(serverPlayer)); + serverPlayer.connection.send(new ClientboundExplodePacket(vec3, optional, particleOptions, explosionSound)); +@@ -1867,14 +1946,17 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public void blockEvent(BlockPos pos, Block block, int eventID, int eventParam) { +- this.blockEvents.add(new BlockEventData(pos, block, eventID, eventParam)); ++ this.getCurrentWorldData().pushBlockEvent(new BlockEventData(pos, block, eventID, eventParam)); // Folia - regionised ticking + } + + private void runBlockEvents() { +- this.blockEventsToReschedule.clear(); ++ List blockEventsToReschedule = new ArrayList<>(64); // Folia - regionised ticking + +- while (!this.blockEvents.isEmpty()) { +- BlockEventData blockEventData = this.blockEvents.removeFirst(); ++ // Folia start - regionised ticking ++ io.papermc.paper.threadedregions.RegionizedWorldData worldRegionData = this.getCurrentWorldData(); ++ BlockEventData blockEventData; ++ while ((blockEventData = worldRegionData.removeFirstBlockEvent()) != null) { ++ // Folia end - regionised ticking + if (this.shouldTickBlocksAt(blockEventData.pos())) { + if (this.doBlockEvent(blockEventData)) { + this.server +@@ -1890,11 +1972,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ); + } + } else { +- this.blockEventsToReschedule.add(blockEventData); ++ blockEventsToReschedule.add(blockEventData); // Folia - regionised ticking + } + } + +- this.blockEvents.addAll(this.blockEventsToReschedule); ++ worldRegionData.pushBlockEvents(blockEventsToReschedule); // Folia - regionised ticking + } + + private boolean doBlockEvent(BlockEventData event) { +@@ -1904,12 +1986,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public LevelTicks getBlockTicks() { +- return this.blockTicks; ++ return this.getCurrentWorldData().getBlockLevelTicks(); // Folia - region ticking + } + + @Override + public LevelTicks getFluidTicks() { +- return this.fluidTicks; ++ return this.getCurrentWorldData().getFluidLevelTicks(); // Folia - region ticking + } + + @Nonnull +@@ -1962,7 +2044,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + double zOffset, + double speed + ) { +- return sendParticlesSource(this.players, sender, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); ++ return sendParticlesSource(this.getLocalPlayers(), sender, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); // Folia - region threading + } + public int sendParticlesSource( + List receivers, +@@ -2045,12 +2127,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + @Nullable + public Entity getEntityOrPart(int id) { + Entity entity = this.getEntities().get(id); +- return entity != null ? entity : this.dragonParts.get(id); ++ return entity != null ? entity : this.dragonParts.get((long)id); // Folia - diff on change + } + + @Override + public Collection dragonParts() { +- return this.dragonParts.values(); ++ return this.dragonParts.values(); // Folia - diff on change + } + + @Nullable +@@ -2105,6 +2187,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper start - Call missing map initialize event and set id + final DimensionDataStorage storage = this.getServer().overworld().getDataStorage(); + ++ synchronized (storage.cache) { // Folia - region threading + final Optional cacheEntry = storage.cache.get(mapId.key()); + if (cacheEntry == null) { // Cache did not contain, try to load and may init + final MapItemSavedData mapData = storage.get(MapItemSavedData.factory(), mapId.key()); // get populates the cache +@@ -2124,6 +2207,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + return null; ++ } // Folia - region threading + // Paper end - Call missing map initialize event and set id + } + +@@ -2178,6 +2262,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + public boolean setChunkForced(int chunkX, int chunkZ, boolean add) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify force loaded chunks off of the global region"); // Folia - region threading + ForcedChunksSavedData forcedChunksSavedData = this.getDataStorage().computeIfAbsent(ForcedChunksSavedData.factory(), "chunks"); + ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); + long packedChunkPos = chunkPos.toLong(); +@@ -2185,7 +2270,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + if (add) { + flag = forcedChunksSavedData.getChunks().add(packedChunkPos); + if (flag) { +- this.getChunk(chunkX, chunkZ); ++ //this.getChunk(chunkX, chunkZ); // Folia - region threading - we must let the chunk load asynchronously + } + } else { + flag = forcedChunksSavedData.getChunks().remove(packedChunkPos); +@@ -2210,11 +2295,24 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + Optional> optional1 = PoiTypes.forState(newState); + if (!Objects.equals(optional, optional1)) { + BlockPos blockPos = pos.immutable(); +- optional.ifPresent(poiType -> this.getServer().execute(() -> { ++ // Folia start - region threading ++ optional.ifPresent(poiType -> { ++ Runnable run = () -> { ++ // Folia end - region threading + this.getPoiManager().remove(blockPos); + DebugPackets.sendPoiRemovedPacket(this, blockPos); +- })); +- optional1.ifPresent(poiType -> this.getServer().execute(() -> { ++ // Folia start - region threading ++ }; ++ // Folia start - region threading ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask( ++ this, blockPos.getX() >> 4, blockPos.getZ() >> 4, run ++ ); ++ }); ++ // Folia end - region threading ++ // Folia start - region threading ++ optional1.ifPresent(poiType -> { ++ Runnable run = () -> { ++ // Folia end - region threading + // Paper start - Remove stale POIs + if (optional.isEmpty() && this.getPoiManager().exists(blockPos, ignored -> true)) { + this.getPoiManager().remove(blockPos); +@@ -2222,7 +2320,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - Remove stale POIs + this.getPoiManager().add(blockPos, (Holder)poiType); + DebugPackets.sendPoiAddedPacket(this, blockPos); +- })); ++ // Folia start - region threading ++ }; ++ // Folia start - region threading ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask( ++ this, blockPos.getX() >> 4, blockPos.getZ() >> 4, run ++ ); ++ // Folia end - region threading ++ }); ++ // Folia end - region threading + } + } + +@@ -2276,7 +2382,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + bufferedWriter.write(String.format(Locale.ROOT, "entities: %s\n", this.moonrise$getEntityLookup().getDebugInfo())); // Paper - rewrite chunk system +- bufferedWriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); ++ //bufferedWriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); // Folia - region threading + bufferedWriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count())); + bufferedWriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count())); + bufferedWriter.write("distance_manager: " + chunkMap.getDistanceManager().getDebugStatus() + "\n"); +@@ -2346,7 +2452,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private void dumpBlockEntityTickers(Writer output) throws IOException { + CsvOutput csvOutput = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("type").build(output); + +- for (TickingBlockEntity tickingBlockEntity : this.blockEntityTickers) { ++ for (TickingBlockEntity tickingBlockEntity : (Iterable)null) { // Folia - region threading + BlockPos pos = tickingBlockEntity.getPos(); + csvOutput.writeRow(pos.getX(), pos.getY(), pos.getZ(), tickingBlockEntity.getType()); + } +@@ -2354,14 +2460,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @VisibleForTesting + public void clearBlockEvents(BoundingBox boundingBox) { +- this.blockEvents.removeIf(blockEventData -> boundingBox.isInside(blockEventData.pos())); ++ this.getCurrentWorldData().removeIfBlockEvents(blockEventData -> boundingBox.isInside(blockEventData.pos())); // Folia - regionised ticking + } + + @Override + public void blockUpdated(BlockPos pos, Block block) { + if (!this.isDebug()) { + // CraftBukkit start +- if (this.populating) { ++ if (this.getCurrentWorldData().populating) { // Folia - region threading + return; + } + // CraftBukkit end +@@ -2410,8 +2516,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.players.size(), + this.moonrise$getEntityLookup().getDebugInfo(), // Paper - rewrite chunk system + getTypeCount(this.moonrise$getEntityLookup().getAll(), entity -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()), // Paper - rewrite chunk system +- this.blockEntityTickers.size(), +- getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), ++ 0, // Folia - region threading ++ "null", // Folia - region threading + this.getBlockTicks().count(), + this.getFluidTicks().count(), + this.gatherChunkSourceStats() +@@ -2463,15 +2569,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + public void startTickingChunk(LevelChunk chunk) { +- chunk.unpackTicks(this.getLevelData().getGameTime()); ++ chunk.unpackTicks(this.getRedstoneGameTime()); // Folia - region threading + } + + public void onStructureStartsAvailable(ChunkAccess chunk) { +- this.server.execute(() -> this.structureCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts())); ++ this.structureCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts()); // Folia - region threading + } + + public PathTypeCache getPathTypeCache() { +- return this.pathTypesByPosCache; ++ return this.getCurrentWorldData().pathTypesByPosCache; // Folia - region threading + } + + @Override +@@ -2489,7 +2595,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return this.moonrise$getAnyChunkIfLoaded(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkPos)) != null; // Paper - rewrite chunk system + } + +- private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { ++ public boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { // Folia - region threaded - make public + // Paper start - rewrite chunk system + final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); + // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded +@@ -2581,7 +2687,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper start - optimize redstone (Alternate Current) + @Override + public alternate.current.wire.WireHandler getWireHandler() { +- return wireHandler; ++ return this.getCurrentWorldData().wireHandler; // Folia - region threading + } + // Paper end - optimize redstone (Alternate Current) + +@@ -2592,18 +2698,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + @Override + public void onDestroyed(Entity entity) { +- ServerLevel.this.getScoreboard().entityRemoved(entity); ++ // ServerLevel.this.getScoreboard().entityRemoved(entity); // Folia - region threading + } + + @Override + public void onTickingStart(Entity entity) { + if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking +- ServerLevel.this.entityTickList.add(entity); ++ ServerLevel.this.getCurrentWorldData().addEntityTickingEntity(entity); // Folia - region threading + } + + @Override + public void onTickingEnd(Entity entity) { +- ServerLevel.this.entityTickList.remove(entity); ++ ServerLevel.this.getCurrentWorldData().removeEntityTickingEntity(entity); // Folia - region threading + // Paper start - Reset pearls when they stop being ticked + if (ServerLevel.this.paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && ServerLevel.this.paperConfig().misc.legacyEnderPearlBehavior && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { + pearl.cachedOwner = null; +@@ -2615,6 +2721,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + @Override + public void onTrackingStart(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot ++ ServerLevel.this.getCurrentWorldData().addLoadedEntity(entity); // Folia - region threading + // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true + if (entity instanceof ServerPlayer serverPlayer) { + ServerLevel.this.players.add(serverPlayer); +@@ -2629,12 +2736,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ); + } + +- ServerLevel.this.navigatingMobs.add(mob); ++ ServerLevel.this.getCurrentWorldData().addNavigatingMob(mob); // Folia - region threading + } + + if (entity instanceof EnderDragon enderDragon) { + for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) { +- ServerLevel.this.dragonParts.put(enderDragonPart.getId(), enderDragonPart); ++ ServerLevel.this.dragonParts.put((long)enderDragonPart.getId(), enderDragonPart); // Folia - diff on change + } + } + +@@ -2657,18 +2764,27 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + @Override + public void onTrackingEnd(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot ++ ServerLevel.this.getCurrentWorldData().removeLoadedEntity(entity); // Folia - region threading + // Spigot start // TODO I don't think this is needed anymore + if (entity instanceof Player player) { + for (final ServerLevel level : ServerLevel.this.getServer().getAllLevels()) { +- for (final Optional savedData : level.getDataStorage().cache.values()) { ++ // Folia start - make map data thread-safe ++ List> worldDataCache; ++ synchronized (level.getDataStorage().cache) { ++ worldDataCache = new java.util.ArrayList<>(level.getDataStorage().cache.values()); ++ } ++ for (final Optional savedData : worldDataCache) { ++ // Folia end - make map data thread-safe + if (savedData.isEmpty() || !(savedData.get() instanceof MapItemSavedData map)) { + continue; + } + ++ synchronized (map) { // Folia - make map data thread-safe + map.carriedByPlayers.remove(player); + if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) { + map.decorations.remove(player.getName().getString()); + } ++ } // Folia - make map data thread-safe + } + } + } +@@ -2699,18 +2815,19 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ); + } + +- ServerLevel.this.navigatingMobs.remove(mob); ++ ServerLevel.this.getCurrentWorldData().removeNavigatingMob(mob); // Folia - region threading + } + + if (entity instanceof EnderDragon enderDragon) { + for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) { +- ServerLevel.this.dragonParts.remove(enderDragonPart.getId()); ++ ServerLevel.this.dragonParts.remove((long)enderDragonPart.getId()); // Folia - diff on change + } + } + + entity.updateDynamicGameEventListener(DynamicGameEventListener::remove); + // CraftBukkit start + entity.valid = false; ++ // Folia - region threading - TODO THIS SHIT + if (!(entity instanceof ServerPlayer)) { + for (ServerPlayer player : ServerLevel.this.server.getPlayerList().players) { // Paper - call onEntityRemove for all online players + player.getBukkitEntity().onEntityRemove(entity); +@@ -2738,11 +2855,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private long lagCompensationTick = MinecraftServer.SERVER_INIT; + + public long getLagCompensationTick() { +- return this.lagCompensationTick; ++ return this.getCurrentWorldData().getLagCompensationTick(); // Folia - region threading + } + + public void updateLagCompensationTick() { +- this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L)); ++ throw new UnsupportedOperationException(); // Folia - region threading + } + // Paper end - lag compensation + } +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 57d432dc9e8d8e9a3e088e7c40b35178c30fe786..f5615c7f7127edda460db9158d6bd4ddad9193f7 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -180,7 +180,7 @@ import org.slf4j.Logger; + + 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 +443,149 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + 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 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 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 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 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 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 +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 +@@ -709,10 +850,17 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + ServerLevel level = this.level().getServer().getLevel(optional.get()); + if (level != null) { + Entity entity = EntityType.loadEntityRecursive( +- compoundTag, level, EntitySpawnReason.LOAD, entity1 -> !level.addWithUUID(entity1) ? null : entity1 ++ compoundTag, level, EntitySpawnReason.LOAD, entity1 -> entity1 // Folia - region threading - delay world add + ); + if (entity != null) { +- placeEnderPearlTicket(level, entity.chunkPosition()); ++ // Folia start - region threading ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( ++ level, entity.chunkPosition().x, entity.chunkPosition().z, () -> { ++ level.addFreshEntityWithPassengers(entity); ++ ServerPlayer.placeEnderPearlTicket(level, entity.chunkPosition()); ++ } ++ ); ++ // Folia end - region threading + } else { + LOGGER.warn("Failed to spawn player ender pearl in level ({}), skipping", optional.get()); + } +@@ -817,12 +965,23 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + + Entity camera = this.getCamera(); + if (camera != this) { +- if (camera.isAlive()) { ++ if (camera.canBeSpectated()) { // Folia - region threading - replace removed check ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(camera) && !camera.isRemoved()) { // Folia - region threading + this.absMoveTo(camera.getX(), camera.getY(), camera.getZ(), camera.getYRot(), camera.getXRot()); + this.serverLevel().getChunkSource().move(this); + if (this.wantsToStopRiding()) { + this.setCamera(this); + } ++ } else { // Folia start - region threading ++ Entity realCamera = camera.getBukkitEntity().getHandleRaw(); ++ if (realCamera != camera) { ++ this.setCamera(this); ++ this.setCamera(realCamera); ++ } else { ++ this.teleportToCameraOffRegion(); ++ } ++ } ++ // Folia end - region threading + } else { + this.setCamera(this); + } +@@ -1357,9 +1516,332 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + } + ++ // 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 respawnComplete, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason reason) { ++ this.respawn(respawnComplete, reason, false); ++ } ++ ++ private void respawn(java.util.function.Consumer 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 spawnPosComplete = ++ new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); ++ boolean[] usedRespawnAnchor = new boolean[1]; ++ ++ // set up post spawn location logic ++ spawnPosComplete.addWaiter((spawnLoc, throwable) -> { ++ // update pos and velocity ++ ServerPlayer.this.setPosRaw(spawnLoc.getX(), spawnLoc.getY(), spawnLoc.getZ()); ++ ServerPlayer.this.setYRot(spawnLoc.getYaw()); ++ ServerPlayer.this.setYHeadRot(spawnLoc.getYaw()); ++ ServerPlayer.this.setXRot(spawnLoc.getPitch()); ++ ServerPlayer.this.setDeltaMovement(Vec3.ZERO); ++ // placeInAsync will update the world ++ ++ this.placeInAsync( ++ origin, ++ // use the load chunk flag just in case the spawn loc isn't loaded, and to ensure the chunks ++ // stay loaded for a bit with the teleport ticket ++ ((org.bukkit.craftbukkit.CraftWorld)spawnLoc.getWorld()).getHandle(), ++ TELEPORT_FLAG_LOAD_CHUNK | TELEPORT_FLAGS_PLAYER_RESPAWN, ++ passengerTree, // note: we expect this to just be the player, no passengers ++ (entity) -> { ++ // now the player is in the world, and can receive sound ++ if (usedRespawnAnchor[0]) { ++ ServerPlayer.this.connection.send( ++ new ClientboundSoundPacket( ++ net.minecraft.sounds.SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, ++ ServerPlayer.this.getX(), ServerPlayer.this.getY(), ServerPlayer.this.getZ(), ++ 1.0F, 1.0F, ServerPlayer.this.serverLevel().getRandom().nextLong() ++ ) ++ ); ++ } ++ // now the respawn logic is complete ++ ++ // last, call the function callback ++ if (respawnComplete != null) { ++ respawnComplete.accept(ServerPlayer.this); ++ } ++ } ++ ); ++ }); ++ ++ // find and modify respawn block state ++ if (respawnWorld == null || respawnPos == null) { ++ // default to regular spawn ++ fudgeSpawnLocation(this.server.getLevel(Level.OVERWORLD), this, spawnPosComplete); ++ } else { ++ // load chunk for block ++ // give at least 1 radius of loaded chunks so that we do not sync load anything ++ int radiusBlocks = 16; ++ respawnWorld.moonrise$loadChunksAsync(respawnPos, radiusBlocks, ++ ca.spottedleaf.concurrentutil.util.Priority.HIGHER, ++ (chunks) -> { ++ ServerPlayer.RespawnPosAngle spawnPos = ServerPlayer.findRespawnAndUseSpawnBlock( ++ respawnWorld, respawnPos, respawnAngle, isRespawnForced, !alive ++ ).orElse(null); ++ if (spawnPos == null) { ++ // no spawn ++ ServerPlayer.this.connection.send( ++ new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F) ++ ); ++ ServerPlayer.this.setRespawnPosition( ++ null, null, 0f, false, false, ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ++ ); ++ // default to regular spawn ++ fudgeSpawnLocation(this.server.getLevel(Level.OVERWORLD), this, spawnPosComplete); ++ return; ++ } ++ ++ boolean isRespawnAnchor = respawnWorld.getBlockState(respawnPos).is(net.minecraft.world.level.block.Blocks.RESPAWN_ANCHOR); ++ boolean isBed = respawnWorld.getBlockState(respawnPos).is(net.minecraft.tags.BlockTags.BEDS); ++ usedRespawnAnchor[0] = !alive && isRespawnAnchor; ++ ++ ServerPlayer.this.setRespawnPosition( ++ respawnWorld.dimension(), respawnPos, respawnAngle, isRespawnForced, false, ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ++ ); ++ ++ // finished now, pass the location on ++ spawnPosComplete.complete( ++ io.papermc.paper.util.MCUtil.toLocation(respawnWorld, spawnPos.position(), spawnPos.yaw(), 0.0f) ++ ); ++ return; ++ } ++ ); ++ } ++ } ++ ++ @Override ++ protected void teleportSyncSameRegion(Vec3 pos, Float yaw, Float pitch, Vec3 velocity) { ++ if (yaw != null) { ++ this.setYRot(yaw.floatValue()); ++ this.setYHeadRot(yaw.floatValue()); ++ } ++ if (pitch != null) { ++ this.setXRot(pitch.floatValue()); ++ } ++ if (velocity != null) { ++ this.setDeltaMovement(velocity); ++ } ++ this.connection.internalTeleport( ++ new net.minecraft.world.entity.PositionMoveRotation( ++ pos, this.getDeltaMovement(), this.getYRot(), this.getXRot() ++ ), ++ java.util.Collections.emptySet() ++ ); ++ this.connection.resetPosition(); ++ this.setOldPosAndRot(); ++ this.resetStoredPositions(); ++ } ++ ++ @Override ++ protected ServerPlayer transformForAsyncTeleport(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity) { ++ // must be manually removed from connections ++ this.serverLevel().getCurrentWorldData().connections.remove(this.connection.connection); ++ this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); ++ ++ this.spawnIn(destination); ++ this.transform(pos, yaw, pitch, velocity); ++ ++ return this; ++ } ++ ++ @Override ++ public void preChangeDimension() { ++ super.preChangeDimension(); ++ this.stopUsingItem(); ++ } ++ ++ @Override ++ protected void placeSingleSync(ServerLevel originWorld, ServerLevel destination, EntityTreeNode treeNode, long teleportFlags) { ++ if (destination == originWorld && (teleportFlags & TELEPORT_FLAGS_PLAYER_RESPAWN) == 0L) { ++ this.unsetRemoved(); ++ destination.addDuringTeleport(this); ++ ++ // must be manually added to connections ++ this.serverLevel().getCurrentWorldData().connections.add(this.connection.connection); ++ ++ // required to set up the pending teleport stuff to the client, and to actually update ++ // the player's position clientside ++ this.connection.internalTeleport( ++ new net.minecraft.world.entity.PositionMoveRotation( ++ this.position(), this.getDeltaMovement(), this.getYRot(), this.getXRot() ++ ), ++ java.util.Collections.emptySet() ++ ); ++ this.connection.resetPosition(); ++ ++ this.postChangeDimension(); ++ } else { ++ // Modelled after PlayerList#respawn ++ ++ // We avoid checking for disconnection here, which means we do not have to add/remove from ++ // the player list here. We can let this be properly handled by the connection handler ++ ++ // pre-add logic ++ PlayerList playerlist = this.server.getPlayerList(); ++ net.minecraft.world.level.storage.LevelData worlddata = destination.getLevelData(); ++ this.connection.send( ++ new ClientboundRespawnPacket( ++ this.createCommonSpawnInfo(destination), ++ (teleportFlags & TELEPORT_FLAGS_PLAYER_RESPAWN) == 0L ? (byte)1 : (byte)0 ++ ) ++ ); ++ // don't bother with the chunk cache radius and simulation distance packets, they are handled ++ // by the chunk loader ++ this.spawnIn(destination); // important that destination != null ++ // we can delay teleport until later, the player position is already set up at the target ++ this.setShiftKeyDown(false); ++ ++ this.connection.send(new net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket( ++ destination.getSharedSpawnPos(), destination.getSharedSpawnAngle() ++ )); ++ this.connection.send(new ClientboundChangeDifficultyPacket( ++ worlddata.getDifficulty(), worlddata.isDifficultyLocked() ++ )); ++ this.connection.send(new ClientboundSetExperiencePacket( ++ this.experienceProgress, this.totalExperience, this.experienceLevel ++ )); ++ ++ playerlist.sendActivePlayerEffects(this); ++ playerlist.sendLevelInfo(this, destination); ++ playerlist.sendPlayerPermissionLevel(this); ++ ++ // regular world add logic ++ this.unsetRemoved(); ++ destination.addDuringTeleport(this); ++ ++ // must be manually added to connections ++ this.serverLevel().getCurrentWorldData().connections.add(this.connection.connection); ++ ++ // required to set up the pending teleport stuff to the client, and to actually update ++ // the player's position clientside ++ this.connection.internalTeleport( ++ new net.minecraft.world.entity.PositionMoveRotation( ++ this.position(), this.getDeltaMovement(), this.getYRot(), this.getXRot() ++ ), ++ java.util.Collections.emptySet() ++ ); ++ this.connection.resetPosition(); ++ ++ // delay callback until after post add logic ++ ++ // post add logic ++ ++ // "Added from changeDimension" ++ this.setHealth(this.getHealth()); ++ playerlist.sendAllPlayerInfo(this); ++ this.onUpdateAbilities(); ++ /*for (MobEffectInstance mobEffect : this.getActiveEffects()) { ++ this.connection.send(new ClientboundUpdateMobEffectPacket(this.getId(), mobEffect, false)); ++ }*/ // handled by sendActivePlayerEffects ++ ++ // Paper start - Reset shield blocking on dimension change ++ if (this.isBlocking()) { ++ this.stopUsingItem(); ++ } ++ // Paper end - Reset shield blocking on dimension change ++ ++ this.triggerDimensionChangeTriggers(originWorld); ++ ++ // finished ++ ++ this.postChangeDimension(); ++ } ++ } ++ ++ @Override ++ public boolean endPortalLogicAsync(BlockPos portalPos) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async"); ++ ++ if (this.level().getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) { ++ if (!this.canPortalAsync(null, false)) { ++ return false; ++ } ++ this.wonGame = true; ++ // TODO is there a better solution to this that DOESN'T skip the credits? ++ this.seenCredits = true; ++ if (!this.seenCredits) { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 0.0F)); ++ } ++ this.exitEndCredits(); ++ return true; ++ } else { ++ return super.endPortalLogicAsync(portalPos); ++ } ++ } ++ ++ @Override ++ protected void prePortalLogic(ServerLevel origin, ServerLevel destination, PortalType type) { ++ super.prePortalLogic(origin, destination, type); ++ if (origin.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.OVERWORLD && destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER) { ++ this.enteredNetherPosition = this.position(); ++ } ++ } ++ // Folia end - region threading ++ + @Nullable + @Override + public ServerPlayer teleport(TeleportTransition teleportTransition) { ++ // Folia start - region threading ++ if (true) { ++ throw new UnsupportedOperationException("Must use teleportAsync while in region threading"); ++ } ++ // Folia end - region threading + if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154 + if (this.isRemoved()) { + return null; +@@ -2397,7 +2879,30 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return (Entity)(this.camera == null ? this : this.camera); + } + ++ // Folia start - region threading ++ private void teleportToCameraOffRegion() { ++ Entity cameraFinal = this.camera; ++ // use the task scheduler, as we don't know where the caller is invoking from ++ if (this != cameraFinal) { ++ this.getBukkitEntity().taskScheduler.schedule((final ServerPlayer newPlayer) -> { ++ io.papermc.paper.threadedregions.TeleportUtils.teleport( ++ newPlayer, false, cameraFinal, null, null, 0L, ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE, null, ++ (final ServerPlayer newerPlayer) -> { ++ return newerPlayer.camera == cameraFinal; ++ } ++ ); ++ }, null, 1L); ++ } // else: do not bother teleporting to self ++ } ++ // Folia end - region threading ++ + public void setCamera(@Nullable Entity entityToSpectate) { ++ // Folia start - region threading ++ if (entityToSpectate != null && (entityToSpectate != this && !entityToSpectate.canBeSpectated())) { ++ return; ++ } ++ // Folia end - region threading + Entity camera = this.getCamera(); + this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate); + if (camera != this.camera) { +@@ -2416,16 +2921,19 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + } + // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity +- if (this.camera.level() instanceof ServerLevel serverLevel) { +- this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit +- } ++ // Folia - region threading - move down + +- if (entityToSpectate != null) { +- this.serverLevel().getChunkSource().move(this); +- } ++ // Folia - region threading - not needed + ++ // Folia start - region threading - handle camera setting better ++ if (this.camera == this ++ || (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.camera) && this.camera.moonrise$getTrackedEntity() != null ++ && this.camera.moonrise$getTrackedEntity().seenBy.contains(this.connection))) { ++ // Folia end - region threading - handle camera setting better + this.connection.send(new ClientboundSetCameraPacket(this.camera)); +- this.connection.resetPosition(); ++ } // Folia - region threading - handle camera setting better ++ //this.connection.resetPosition(); // Folia - region threading - not needed ++ this.teleportToCameraOffRegion(); // Folia - region threading - moved down + } + } + +@@ -2896,11 +3404,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + + 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 getEnderPearls() { +@@ -3054,7 +3562,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + 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; +diff --git a/net/minecraft/server/level/ServerPlayerGameMode.java b/net/minecraft/server/level/ServerPlayerGameMode.java +index 623c069f1fe079e020c6391a3db1a3d95cd3dbf5..61804cdb6be06b1b3316e563df57f0b38268958a 100644 +--- a/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -114,7 +114,7 @@ public class ServerPlayerGameMode { + // 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 +126,7 @@ public class ServerPlayerGameMode { + } + } 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 +369,7 @@ public class ServerPlayerGameMode { + } 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 +395,8 @@ public class ServerPlayerGameMode { + // return true; // CraftBukkit + } + // CraftBukkit start +- java.util.List 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 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 + } +diff --git a/net/minecraft/server/level/TicketType.java b/net/minecraft/server/level/TicketType.java +index 8f12a4df5d63ecd11e6e615d910b6e3f6dde5f3c..f8b74eaf534c6264ce018a6826c3d035089e7d30 100644 +--- a/net/minecraft/server/level/TicketType.java ++++ b/net/minecraft/server/level/TicketType.java +@@ -17,10 +17,18 @@ public class TicketType { + public static final TicketType FORCED = create("forced", Comparator.comparingLong(ChunkPos::toLong)); + public static final TicketType PORTAL = create("portal", Vec3i::compareTo, 300); + public static final TicketType ENDER_PEARL = create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40); +- public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); ++ public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 5); // Folia - region threading + public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type ++ // Folia start - region threading ++ public static final TicketType LOGIN = create("folia:login", (u1, u2) -> 0, 20); ++ public static final TicketType DELAYED = create("folia:delay", (u1, u2) -> 0, 5); ++ public static final TicketType END_GATEWAY_EXIT_SEARCH = create("folia:end_gateway_exit_search", Long::compareTo); ++ public static final TicketType NETHER_PORTAL_DOUBLE_CHECK = create("folia:nether_portal_double_check", Long::compareTo); ++ public static final TicketType TELEPORT_HOLD_TICKET = create("folia:teleport_hold_ticket", Long::compareTo); ++ public static final TicketType REGION_SCHEDULER_API_HOLD = create("folia:region_scheduler_api_hold", (a, b) -> 0); ++ // Folia end - region threading + + public static TicketType create(String name, Comparator comparator) { + return new TicketType<>(name, comparator, 0L); +diff --git a/net/minecraft/server/level/WorldGenRegion.java b/net/minecraft/server/level/WorldGenRegion.java +index 7fa41dea184b01891f45d8e404bc1cba19cf1bcf..43de96cc3c2b1259b1edb5feae3f202dea65dcdf 100644 +--- a/net/minecraft/server/level/WorldGenRegion.java ++++ b/net/minecraft/server/level/WorldGenRegion.java +@@ -107,6 +107,13 @@ public class WorldGenRegion implements WorldGenLevel { + 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 cache, ChunkStep generatingStep, ChunkAccess center) { + this.generatingStep = generatingStep; +@@ -118,6 +125,7 @@ public class WorldGenRegion implements WorldGenLevel { + 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) { +diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index e71c1a564e5d4ac43460f89879ff709ee685706f..6eca15223b92aedac74233db886e2c1248750e2c 100644 +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -96,6 +96,10 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + } + ++ // 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 +108,18 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + 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 +342,8 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + 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 +374,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + 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 +387,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + + 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 + +diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +index 2e9eb04c7c4342393c05339906c267bca9ff29b1..00fb8a5dda1f305a0e0f947bbb75a3f40b5318cc 100644 +--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +@@ -47,6 +47,7 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis + 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 +161,58 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis + } + + 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 data = new org.apache.commons.lang3.mutable.MutableObject<>(); ++ org.apache.commons.lang3.mutable.MutableObject lastKnownName = new org.apache.commons.lang3.mutable.MutableObject<>(); ++ ca.spottedleaf.concurrentutil.completable.CallbackCompletable 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 +diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java +index bd07e6a5aa1883786d789ea71711a0c0c0a95c26..09469ad131622158fe5579216fc4164251ff2220 100644 +--- a/net/minecraft/server/network/ServerConnectionListener.java ++++ b/net/minecraft/server/network/ServerConnectionListener.java +@@ -167,12 +167,15 @@ public class ServerConnectionListener { + } + // 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 +245,7 @@ public class ServerConnectionListener { + // 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 +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 660f9d44739909150635beaa2082e85dfde915c4..1f031366afa973fa3ed027505d149737febd169e 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -292,10 +292,10 @@ public class ServerGamePacketListenerImpl + 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 +313,16 @@ public class ServerGamePacketListenerImpl + 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 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 +335,12 @@ public class ServerGamePacketListenerImpl + + @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 +389,7 @@ public class ServerGamePacketListenerImpl + 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 +425,19 @@ public class ServerGamePacketListenerImpl + 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 +545,10 @@ public class ServerGamePacketListenerImpl + // 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 +615,7 @@ public class ServerGamePacketListenerImpl + } + + 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,11 +627,19 @@ public class ServerGamePacketListenerImpl + } + 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(); + if (!this.hasMoved) { +@@ -635,7 +670,7 @@ public class ServerGamePacketListenerImpl + + // 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 +678,7 @@ public class ServerGamePacketListenerImpl + // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. + // We only do this if the Event was not cancelled. + if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { +- this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); ++ this.player.getBukkitEntity().teleportAsync(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading + return; + } + +@@ -824,7 +859,7 @@ public class ServerGamePacketListenerImpl + } + + // This needs to be on main +- this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringReader)); ++ this.player.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> this.sendServerSuggestions(packet, stringReader), null, 1L); // Folia - region threading + } else if (!completions.isEmpty()) { + final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringReader.getTotalLength()); + final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1); +@@ -1207,11 +1242,11 @@ public class ServerGamePacketListenerImpl + } + // Paper end - Book size limits + // CraftBukkit start +- if (this.lastBookTick + 20 > MinecraftServer.currentTick) { ++ if (this.lastBookTick + 20 > this.lastTick) { // Folia - region threading + this.disconnectAsync(Component.literal("Book edited too quickly!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect + return; + } +- this.lastBookTick = MinecraftServer.currentTick; ++ this.lastBookTick = this.lastTick; // Folia - region threading + // CraftBukkit end + int slot = packet.slot(); + if (Inventory.isHotbarSlot(slot) || slot == 40) { +@@ -1222,7 +1257,22 @@ public class ServerGamePacketListenerImpl + Consumer> consumer = optional.isPresent() + ? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot) + : texts -> this.updateBookContents(texts, slot); +- this.filterTextPacket(list).thenAcceptAsync(consumer, this.server); ++ // Folia start - region threading ++ this.filterTextPacket(list).thenAcceptAsync( ++ consumer, ++ (Runnable run) -> { ++ this.player.getBukkitEntity().taskScheduler.schedule( ++ (player) -> { ++ run.run(); ++ }, ++ null, 1L); ++ } ++ ).whenComplete((Object res, Throwable thr) -> { ++ if (thr != null) { ++ LOGGER.error("Failed to handle book update packet", thr); ++ } ++ }); ++ // Folia end - region threading + } + } + +@@ -1348,9 +1398,10 @@ public class ServerGamePacketListenerImpl + int i = this.receivedMovePacketCount - this.knownMovePacketCount; + + // CraftBukkit start - handle custom speeds and skipped ticks +- this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; ++ int currTick = (int)(Util.getMillis() / 50); // Folia - region threading ++ this.allowedPlayerTicks += currTick - this.lastTick; // Folia - region threading + this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); +- this.lastTick = (int) (System.currentTimeMillis() / 50); ++ this.lastTick = (int) currTick; // Folia - region threading + + if (i > Math.max(this.allowedPlayerTicks, 5)) { + LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i); +@@ -1539,7 +1590,7 @@ public class ServerGamePacketListenerImpl + + // If the event is cancelled we move the player back to their old location. + if (event.isCancelled()) { +- this.teleport(from); ++ this.player.getBukkitEntity().teleportAsync(from, PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading + return; + } + +@@ -1547,7 +1598,7 @@ public class ServerGamePacketListenerImpl + // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. + // We only do this if the Event was not cancelled. + if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { +- this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); ++ this.player.getBukkitEntity().teleportAsync(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading + return; + } + +@@ -1806,9 +1857,9 @@ public class ServerGamePacketListenerImpl + if (!this.player.isSpectator()) { + // limit how quickly items can be dropped + // If the ticks aren't the same then the count starts from 0 and we update the lastDropTick. +- if (this.lastDropTick != MinecraftServer.currentTick) { ++ if (this.lastDropTick != io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick()) { // Folia - region threading + this.dropCount = 0; +- this.lastDropTick = MinecraftServer.currentTick; ++ this.lastDropTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - region threading + } else { + // Else we increment the drop count and check the amount. + this.dropCount++; +@@ -1836,7 +1887,7 @@ public class ServerGamePacketListenerImpl + case ABORT_DESTROY_BLOCK: + case STOP_DESTROY_BLOCK: + // Paper start - Don't allow digging into unloaded chunks +- if (this.player.level().getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null) { ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player.serverLevel(), pos.getX() >> 4, pos.getZ() >> 4, 8) || this.player.level().getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null) { // Folia - region threading - don't destroy blocks not owned + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); + return; + } +@@ -1918,7 +1969,7 @@ public class ServerGamePacketListenerImpl + } + // Paper end - improve distance check + BlockPos blockPos = hitResult.getBlockPos(); +- if (this.player.canInteractWithBlock(blockPos, 1.0)) { ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player.serverLevel(), blockPos.getX() >> 4, blockPos.getZ() >> 4, 8) && this.player.canInteractWithBlock(blockPos, 1.0)) { // Folia - do not allow players to interact with blocks outside the current region + Vec3 vec3 = location.subtract(Vec3.atCenterOf(blockPos)); + double d = 1.0000001; + if (Math.abs(vec3.x()) < 1.0000001 && Math.abs(vec3.y()) < 1.0000001 && Math.abs(vec3.z()) < 1.0000001) { +@@ -2039,7 +2090,7 @@ public class ServerGamePacketListenerImpl + for (ServerLevel serverLevel : this.server.getAllLevels()) { + Entity entity = packet.getEntity(serverLevel); + if (entity != null) { +- this.player.teleportTo(serverLevel, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit ++ io.papermc.paper.threadedregions.TeleportUtils.teleport(this.player, false, entity, null, null, Entity.TELEPORT_FLAG_LOAD_CHUNK, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE, null); // Folia - region threading + return; + } + } +@@ -2071,7 +2122,7 @@ public class ServerGamePacketListenerImpl + } + // CraftBukkit end + LOGGER.info("{} lost connection: {}", this.player.getName().getString(), details.reason().getString()); +- this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent ++ if (!this.waitingForSwitchToConfig) this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent // Folia - region threading + super.onDisconnect(details, quitMessage); // Paper - Fix kick event leave message not being sent + } + +@@ -2080,6 +2131,8 @@ public class ServerGamePacketListenerImpl + this.removePlayerFromWorld(null); + } + ++ public boolean hackSwitchingConfig; // Folia - rewrite login process ++ + private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) { + // Paper end - Fix kick event leave message not being sent + this.chatMessageChain.close(); +@@ -2093,6 +2146,8 @@ public class ServerGamePacketListenerImpl + this.player.disconnect(); + // Paper start - Adventure + quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used ++ if (!this.hackSwitchingConfig) this.disconnectPos = this.player.chunkPosition(); // Folia - region threading - note: only set after removing, since it can tick the player ++ if (!this.hackSwitchingConfig) this.player.serverLevel().chunkSource.addTicketAtLevel(DISCONNECT_TICKET, this.disconnectPos, ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, this.disconnectTicketId); // Folia - region threading - force chunk to be loaded so that the region is not lost + if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); + // Paper end - Adventure +@@ -2331,7 +2386,7 @@ public class ServerGamePacketListenerImpl + this.player.resetLastActionTime(); + // CraftBukkit start + if (sync) { +- this.server.execute(handler); ++ this.player.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> handler.run(), null, 1L); // Folia - region threading + } else { + handler.run(); + } +@@ -2386,7 +2441,7 @@ public class ServerGamePacketListenerImpl + String originalFormat = event.getFormat(), originalMessage = event.getMessage(); + this.cserver.getPluginManager().callEvent(event); + +- if (PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) { ++ if (false && PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) { // Folia - region threading + // Evil plugins still listening to deprecated event + final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients()); + queueEvent.setCancelled(event.isCancelled()); +@@ -2483,6 +2538,7 @@ public class ServerGamePacketListenerImpl + if (rawMessage.isEmpty()) { + LOGGER.warn("{} tried to send an empty message", this.player.getScoreboardName()); + } else if (this.getCraftPlayer().isConversing()) { ++ if (true) throw new UnsupportedOperationException(); // Folia - region threading + final String conversationInput = rawMessage; + this.server.processQueue.add(() -> ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput)); + } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Re-add "Command Only" flag check +@@ -2708,8 +2764,25 @@ public class ServerGamePacketListenerImpl + // Spigot end + + public void switchToConfig() { +- this.waitingForSwitchToConfig = true; ++ // Folia start - rewrite login process ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.player, "Cannot switch config off-main"); ++ if (io.papermc.paper.threadedregions.RegionizedServer.isGlobalTickThread()) { ++ throw new IllegalStateException("Cannot switch config while on global tick thread"); ++ } ++ // Folia end - rewrite login process ++ // Folia start - rewrite login process - fix bad ordering of this field write - move after removed from world ++ // the field write ordering is bad as it allows the client to send the response packet before the player is ++ // removed from the world ++ // Folia end - rewrite login process - fix bad ordering of this field write - move after removed from world ++ try { // Folia - rewrite login process - move connection ownership to global region ++ this.hackSwitchingConfig = true; // Folia - rewrite login process - avoid adding logout ticket here and retiring scheduler + this.removePlayerFromWorld(); ++ } finally { // Folia start - rewrite login process - move connection ownership to global region ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.player.serverLevel().getCurrentWorldData(); ++ worldData.connections.remove(this.connection); ++ // once waitingForSwitchToConfig is set, the global tick thread will own the connection ++ } // Folia end - rewrite login process - move connection ownership to global region ++ this.waitingForSwitchToConfig = true; // Folia - rewrite login process - fix bad ordering of this field write - moved down + this.send(ClientboundStartConfigurationPacket.INSTANCE); + this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); + } +@@ -2734,7 +2807,7 @@ public class ServerGamePacketListenerImpl + // Spigot end + this.player.resetLastActionTime(); + this.player.setShiftKeyDown(packet.isUsingSecondaryAction()); +- if (target != null) { ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(target) && target != null) { // Folia - region threading - do not allow interaction of entities outside the current region + if (!serverLevel.getWorldBorder().isWithinBounds(target.blockPosition())) { + return; + } +@@ -2866,6 +2939,12 @@ public class ServerGamePacketListenerImpl + switch (action) { + case PERFORM_RESPAWN: + if (this.player.wonGame) { ++ // Folia start - region threading ++ if (true) { ++ this.player.exitEndCredits(); ++ return; ++ } ++ // Folia end - region threading + this.player.wonGame = false; + this.player = this.server.getPlayerList().respawn(this.player, true, Entity.RemovalReason.CHANGED_DIMENSION, RespawnReason.END_PORTAL); // CraftBukkit + this.resetPosition(); +@@ -2875,6 +2954,17 @@ public class ServerGamePacketListenerImpl + return; + } + ++ // Folia start - region threading ++ if (true) { ++ this.player.respawn((ServerPlayer player) -> { ++ if (ServerGamePacketListenerImpl.this.server.isHardcore()) { ++ ServerGamePacketListenerImpl.this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent ++ ((GameRules.BooleanValue) ServerGamePacketListenerImpl.this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, ServerGamePacketListenerImpl.this.player.serverLevel()); // CraftBukkit - per-world ++ } ++ }, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH); ++ return; ++ } ++ // Folia end - region threading + this.player = this.server.getPlayerList().respawn(this.player, false, Entity.RemovalReason.KILLED, RespawnReason.DEATH); // CraftBukkit + this.resetPosition(); + if (this.server.isHardcore()) { +@@ -3448,7 +3538,21 @@ public class ServerGamePacketListenerImpl + } + List 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)list1), this.server); ++ // Folia start - region threading ++ this.filterTextPacket(list).thenAcceptAsync((list1) -> { ++ this.updateSignText(packet, (List)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) { +diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 6689aeacf50d1498e8d23cce696fb4fecbd1cf39..6173f704b0d093813ec67eb231c75be49a462e7d 100644 +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -111,7 +111,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + // 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 +254,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + ); + } + +- 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 +366,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + 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 +diff --git a/net/minecraft/server/players/BanListEntry.java b/net/minecraft/server/players/BanListEntry.java +index e111adec2116f922fe67ee434635e50c60dad15c..851d3ae5d37541e6455b83b3300d630e8f6d5c83 100644 +--- a/net/minecraft/server/players/BanListEntry.java ++++ b/net/minecraft/server/players/BanListEntry.java +@@ -9,7 +9,7 @@ import javax.annotation.Nullable; + import net.minecraft.network.chat.Component; + + public abstract class BanListEntry extends StoredUserEntry { +- public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT); ++ public static final ThreadLocal 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 +30,7 @@ public abstract class BanListEntry extends StoredUserEntry { + + 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 +40,7 @@ public abstract class BanListEntry extends StoredUserEntry { + + 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 +75,9 @@ public abstract class BanListEntry extends StoredUserEntry { + + @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 +86,7 @@ public abstract class BanListEntry extends StoredUserEntry { + 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 + } +diff --git a/net/minecraft/server/players/OldUsersConverter.java b/net/minecraft/server/players/OldUsersConverter.java +index 7dbcd9d96f052bb10127ad2b061154c23cc9ffd4..20d895ed04cd2263560f91ef38dda6aa866bc603 100644 +--- a/net/minecraft/server/players/OldUsersConverter.java ++++ b/net/minecraft/server/players/OldUsersConverter.java +@@ -469,7 +469,7 @@ public class OldUsersConverter { + 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; + } +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 5a4960fdbd97d830ac79845697eea9372c48a13b..7b13a9e7d38efe7786023747f55ebf5a2ba80688 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -110,10 +110,10 @@ public abstract class PlayerList { + 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 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 players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety +- private final Map playersByUUID = Maps.newHashMap(); ++ private final Map 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 +137,60 @@ public abstract class PlayerList { + private final Map 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 connectionByName = new java.util.HashMap<>(); ++ private final Map connectionById = new java.util.HashMap<>(); ++ private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet 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 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 +203,7 @@ public abstract class PlayerList { + + 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 data, org.apache.commons.lang3.mutable.MutableObject lastKnownName, ca.spottedleaf.concurrentutil.completable.CallbackCompletable 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 +275,41 @@ public abstract class PlayerList { + 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 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 +332,11 @@ public abstract class PlayerList { + 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 +370,7 @@ public abstract class PlayerList { + 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 +410,7 @@ public abstract class PlayerList { + 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 +425,7 @@ public abstract class PlayerList { + ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); // Paper - Add Listing API for Player + + final List 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 +474,7 @@ public abstract class PlayerList { + // 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 +564,7 @@ public abstract class PlayerList { + + 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 +599,7 @@ public abstract class PlayerList { + // 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 +648,7 @@ public abstract class PlayerList { + } + + 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 +666,7 @@ public abstract class PlayerList { + // 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 +690,12 @@ public abstract class PlayerList { + + 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 +715,7 @@ public abstract class PlayerList { + 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 +729,7 @@ public abstract class PlayerList { + 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 +739,7 @@ public abstract class PlayerList { + // 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 +788,11 @@ public abstract class PlayerList { + 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 +963,10 @@ public abstract class PlayerList { + 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 +975,17 @@ public abstract class PlayerList { + + // 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 +1022,7 @@ public abstract class PlayerList { + 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 +1031,11 @@ public abstract class PlayerList { + } + + public String[] getPlayerNamesArray() { ++ List 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 +1053,9 @@ public abstract class PlayerList { + 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 +1063,9 @@ public abstract class PlayerList { + 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 +1128,7 @@ public abstract class PlayerList { + } + + public void broadcast(@Nullable Player except, double x, double y, double z, double radius, ResourceKey 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 +1153,15 @@ public abstract class PlayerList { + 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 +1280,20 @@ public abstract class PlayerList { + } + + 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 +1303,7 @@ public abstract class PlayerList { + // 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); +diff --git a/net/minecraft/server/players/StoredUserList.java b/net/minecraft/server/players/StoredUserList.java +index d445e8f126f077d8419c52fa5436ea963a1a42a4..cabbc68f7cd5fd326c7ffd3b02b3ec4a4390f5b0 100644 +--- a/net/minecraft/server/players/StoredUserList.java ++++ b/net/minecraft/server/players/StoredUserList.java +@@ -97,6 +97,7 @@ public abstract class StoredUserList> { + } + + 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 +105,11 @@ public abstract class StoredUserList> { + 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 +134,6 @@ public abstract class StoredUserList> { + } + // Spigot end + } ++ } // Folia - region threading + } + } +diff --git a/net/minecraft/util/SpawnUtil.java b/net/minecraft/util/SpawnUtil.java +index f6fad24af884c8a37723c57718cee0096443efe6..ef75a71aee2576254e2c0752cc759c9637af9d69 100644 +--- a/net/minecraft/util/SpawnUtil.java ++++ b/net/minecraft/util/SpawnUtil.java +@@ -83,7 +83,7 @@ public class SpawnUtil { + return Optional.of(mob); + } + +- mob.discard(null); // CraftBukkit - add Bukkit remove cause ++ //mob.discard(null); // CraftBukkit - add Bukkit remove cause // Folia - region threading + } + } + } +diff --git a/net/minecraft/world/RandomSequences.java b/net/minecraft/world/RandomSequences.java +index f8e93fe461794058a26c90510cbd7698fa43b8f7..c1a62556546b05f79dad37875993c19bf9bb7c67 100644 +--- a/net/minecraft/world/RandomSequences.java ++++ b/net/minecraft/world/RandomSequences.java +@@ -21,7 +21,7 @@ public class RandomSequences extends SavedData { + private int salt; + private boolean includeWorldSeed = true; + private boolean includeSequenceId = true; +- private final Map sequences = new Object2ObjectOpenHashMap<>(); ++ private final Map sequences = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - region threading + + public static SavedData.Factory factory(long seed) { + return new SavedData.Factory<>( +@@ -120,61 +120,61 @@ public class RandomSequences extends SavedData { + @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 +diff --git a/net/minecraft/world/damagesource/CombatTracker.java b/net/minecraft/world/damagesource/CombatTracker.java +index d3de87eaf0eb84af77165391c7b94085d425f21d..019d1435cfe7769ed85b40c1c19d2d271d96f913 100644 +--- a/net/minecraft/world/damagesource/CombatTracker.java ++++ b/net/minecraft/world/damagesource/CombatTracker.java +@@ -53,7 +53,7 @@ public class CombatTracker { + } + + 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 +80,7 @@ public class CombatTracker { + + @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() { +diff --git a/net/minecraft/world/damagesource/DamageSource.java b/net/minecraft/world/damagesource/DamageSource.java +index daeb344f9e21e8155326b47aeccd4cfc07da42cd..9a204bbd91f48a3097471d1273d65c185b353fcc 100644 +--- a/net/minecraft/world/damagesource/DamageSource.java ++++ b/net/minecraft/world/damagesource/DamageSource.java +@@ -163,12 +163,12 @@ public class DamageSource { + 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); +diff --git a/net/minecraft/world/damagesource/FallLocation.java b/net/minecraft/world/damagesource/FallLocation.java +index a3c7d68469075bf8d33f2016149a181b0fb87e0e..73c581d3ee21d8fa96eae3e47afd6ce204e03160 100644 +--- a/net/minecraft/world/damagesource/FallLocation.java ++++ b/net/minecraft/world/damagesource/FallLocation.java +@@ -35,7 +35,7 @@ public record FallLocation(String id) { + @Nullable + public static FallLocation getCurrentFallLocation(LivingEntity entity) { + Optional 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 { +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 216482b4bb705520411bdeaa58f6044d05190eb6..7e7d232723362e94452ea0768c23d3afbd7c569d 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -145,7 +145,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + // Paper start - Share random for entities to make them more random +- public static RandomSource SHARED_RANDOM = new RandomRandomSource(); ++ public static RandomSource SHARED_RANDOM = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Folia - region threading + // Paper start - replace random + private static final class RandomRandomSource extends ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom { + public RandomRandomSource() { +@@ -175,7 +175,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason + + public boolean collisionLoadChunks = false; // Paper +- private @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; ++ private volatile @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; // Folia - region threading + + public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() { + if (this.bukkitEntity == null) { +@@ -294,7 +294,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + private boolean hasGlowingTag; + private final Set tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl + private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0}; +- private long pistonDeltasGameTime; ++ private long pistonDeltasGameTime = Long.MIN_VALUE; // Folia - region threading + private EntityDimensions dimensions; + private float eyeHeight; + public boolean isInPowderSnow; +@@ -521,6 +521,23 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + // Paper end - optimise entity tracker ++ // Folia start - region ticking ++ public void updateTicks(long fromTickOffset, long fromRedstoneTimeOffset) { ++ if (this.activatedTick != Integer.MIN_VALUE) { ++ this.activatedTick += fromTickOffset; ++ } ++ if (this.activatedImmunityTick != Integer.MIN_VALUE) { ++ this.activatedImmunityTick += fromTickOffset; ++ } ++ if (this.pistonDeltasGameTime != Long.MIN_VALUE) { ++ this.pistonDeltasGameTime += fromRedstoneTimeOffset; ++ } ++ } ++ ++ public boolean canBeSpectated() { ++ return !this.getBukkitEntity().taskScheduler.isRetired(); ++ } ++ // Folia end - region ticking + + public Entity(EntityType entityType, Level level) { + this.type = entityType; +@@ -651,8 +668,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // due to interactions on the client. + public void resendPossiblyDesyncedEntityData(net.minecraft.server.level.ServerPlayer player) { + if (player.getBukkitEntity().canSee(this.getBukkitEntity())) { +- ServerLevel world = (net.minecraft.server.level.ServerLevel)this.level(); +- net.minecraft.server.level.ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.getId()); ++ net.minecraft.server.level.ChunkMap.TrackedEntity tracker = this.moonrise$getTrackedEntity(); // Folia - region threading + if (tracker == null) { + return; + } +@@ -819,7 +835,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle + if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities +- this.handlePortal(); ++ //this.handlePortal(); // Folia - region threading + } + } + // CraftBukkit end +@@ -837,7 +853,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.boardingCooldown--; + } + +- if (this instanceof ServerPlayer) this.handlePortal(); // CraftBukkit - // Moved up to postTick ++ //if (this instanceof ServerPlayer) this.handlePortal(); // CraftBukkit - // Moved up to postTick // Folia - region threading - ONLY allow in postTick() + if (this.canSpawnSprintParticle()) { + this.spawnSprintParticle(); + } +@@ -1100,8 +1116,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } else { + this.wasOnFire = this.isOnFire(); + if (type == MoverType.PISTON) { +- this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20); // Paper - EAR 2 +- this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20); // Paper - EAR 2 ++ this.activatedTick = Math.max(this.activatedTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 20); // Paper - EAR 2 // Folia - region threading ++ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 20); // Paper - EAR 2 // Folia - region threading + movement = this.limitPistonMovement(movement); + if (movement.equals(Vec3.ZERO)) { + return; +@@ -1400,7 +1416,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (pos.lengthSqr() <= 1.0E-7) { + return pos; + } else { +- long gameTime = this.level().getGameTime(); ++ long gameTime = this.level().getRedstoneGameTime(); // Folia - region threading + if (gameTime != this.pistonDeltasGameTime) { + Arrays.fill(this.pistonDeltas, 0.0); + this.pistonDeltasGameTime = gameTime; +@@ -3034,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()); +@@ -3055,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(); + } +@@ -3122,7 +3140,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.passengers = ImmutableList.copyOf(list); + } + +- this.gameEvent(GameEvent.ENTITY_MOUNT, passenger); ++ if (!passenger.hasNullCallback()) this.gameEvent(GameEvent.ENTITY_MOUNT, passenger); // Folia - region threading - do not fire game events for entities not added + } + } + +@@ -3136,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) { +@@ -3163,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 { +@@ -3170,7 +3190,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + passenger.boardingCooldown = 60; +- this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger); ++ if (!passenger.hasNullCallback()) this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger); // Folia - region threading - do not fire game events for entities not added + } + return true; // CraftBukkit + } +@@ -3254,7 +3274,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + +- protected void handlePortal() { ++ public boolean handlePortal() { // Folia - region threading - public, ret type -> boolean + if (this.level() instanceof ServerLevel serverLevel) { + this.processPortalCooldown(); + if (this.portalProcess != null) { +@@ -3262,21 +3282,20 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("portal"); + this.setPortalCooldown(); +- TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this); +- if (portalDestination != null) { +- ServerLevel level = portalDestination.newLevel(); +- if (this instanceof ServerPlayer // CraftBukkit - always call event for players +- || (level != null && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)))) { // CraftBukkit +- this.teleport(portalDestination); +- } ++ // Folia start - region threading ++ try { ++ return this.portalProcess.portalAsync(serverLevel, this); ++ } finally { ++ profilerFiller.pop(); + } +- +- profilerFiller.pop(); ++ // Folia end - region threading + } else if (this.portalProcess.hasExpired()) { + this.portalProcess = null; + } + } + } ++ ++ return false; // Folia - region threading + } + + public int getDimensionChangingDelay() { +@@ -3416,6 +3435,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + @Nullable + public PlayerTeam getTeam() { ++ // Folia start - region threading ++ if (true) { ++ return null; ++ } ++ // Folia end - region threading + if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper - Perf: Disable Scoreboards for non players by default + return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName()); + } +@@ -3722,8 +3746,793 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.portalProcess = entity.portalProcess; + } + ++ // Folia start - region threading ++ public static class EntityTreeNode { ++ @Nullable ++ public EntityTreeNode parent; ++ public Entity root; ++ @Nullable ++ public EntityTreeNode[] passengers; ++ ++ public EntityTreeNode(EntityTreeNode parent, Entity root) { ++ this.parent = parent; ++ this.root = root; ++ } ++ ++ public EntityTreeNode(EntityTreeNode parent, Entity root, EntityTreeNode[] passengers) { ++ this.parent = parent; ++ this.root = root; ++ this.passengers = passengers; ++ } ++ ++ public List getFullTree() { ++ List ret = new java.util.ArrayList<>(); ++ ret.add(this); ++ ++ // this is just a BFS except we don't remove from head, we just advance down the list ++ for (int i = 0; i < ret.size(); ++i) { ++ EntityTreeNode node = ret.get(i); ++ ++ EntityTreeNode[] passengers = node.passengers; ++ if (passengers == null) { ++ continue; ++ } ++ for (EntityTreeNode passenger : passengers) { ++ ret.add(passenger); ++ } ++ } ++ ++ return ret; ++ } ++ ++ public void restore() { ++ java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); ++ queue.add(this); ++ ++ EntityTreeNode curr; ++ while ((curr = queue.pollFirst()) != null) { ++ EntityTreeNode[] passengers = curr.passengers; ++ if (passengers == null) { ++ continue; ++ } ++ ++ List newPassengers = new java.util.ArrayList<>(); ++ for (EntityTreeNode passenger : passengers) { ++ newPassengers.add(passenger.root); ++ passenger.root.vehicle = curr.root; ++ } ++ ++ curr.root.passengers = ImmutableList.copyOf(newPassengers); ++ } ++ } ++ ++ public void addTracker() { ++ for (final EntityTreeNode node : this.getFullTree()) { ++ if (node.root.moonrise$getTrackedEntity() != null) { ++ for (final ServerPlayer player : node.root.level.getLocalPlayers()) { ++ node.root.moonrise$getTrackedEntity().updatePlayer(player); ++ } ++ } ++ } ++ } ++ ++ public void clearTracker() { ++ for (final EntityTreeNode node : this.getFullTree()) { ++ if (node.root.moonrise$getTrackedEntity() != null) { ++ node.root.moonrise$getTrackedEntity().moonrise$removeNonTickThreadPlayers(); ++ for (final ServerPlayer player : node.root.level.getLocalPlayers()) { ++ node.root.moonrise$getTrackedEntity().removePlayer(player); ++ } ++ } ++ } ++ } ++ ++ public void adjustRiders(boolean teleport) { ++ java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); ++ queue.add(this); ++ ++ EntityTreeNode curr; ++ while ((curr = queue.pollFirst()) != null) { ++ EntityTreeNode[] passengers = curr.passengers; ++ if (passengers == null) { ++ continue; ++ } ++ ++ for (EntityTreeNode passenger : passengers) { ++ curr.root.positionRider(passenger.root, teleport ? Entity::moveTo : Entity::setPos); ++ } ++ } ++ } ++ } ++ ++ public void repositionAllPassengers(boolean teleport) { ++ this.makePassengerTree().adjustRiders(teleport); ++ } ++ ++ protected EntityTreeNode makePassengerTree() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot read passengers off of the main thread"); ++ ++ EntityTreeNode root = new EntityTreeNode(null, this); ++ java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); ++ queue.add(root); ++ EntityTreeNode curr; ++ while ((curr = queue.pollFirst()) != null) { ++ Entity vehicle = curr.root; ++ List passengers = vehicle.passengers; ++ if (passengers.isEmpty()) { ++ continue; ++ } ++ ++ EntityTreeNode[] treePassengers = new EntityTreeNode[passengers.size()]; ++ curr.passengers = treePassengers; ++ ++ for (int i = 0; i < passengers.size(); ++i) { ++ Entity passenger = passengers.get(i); ++ queue.addLast(treePassengers[i] = new EntityTreeNode(curr, passenger)); ++ } ++ } ++ ++ return root; ++ } ++ ++ protected EntityTreeNode detachPassengers() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot adjust passengers/vehicle off of the main thread"); ++ ++ EntityTreeNode root = new EntityTreeNode(null, this); ++ java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); ++ queue.add(root); ++ EntityTreeNode curr; ++ while ((curr = queue.pollFirst()) != null) { ++ Entity vehicle = curr.root; ++ List passengers = vehicle.passengers; ++ if (passengers.isEmpty()) { ++ continue; ++ } ++ ++ vehicle.passengers = ImmutableList.of(); ++ ++ EntityTreeNode[] treePassengers = new EntityTreeNode[passengers.size()]; ++ curr.passengers = treePassengers; ++ ++ for (int i = 0; i < passengers.size(); ++i) { ++ Entity passenger = passengers.get(i); ++ passenger.vehicle = null; ++ queue.addLast(treePassengers[i] = new EntityTreeNode(curr, passenger)); ++ } ++ } ++ ++ return root; ++ } ++ ++ /** ++ * This flag will perform an async load on the chunks determined by ++ * the entity's bounding box before teleporting the entity. ++ */ ++ public static final long TELEPORT_FLAG_LOAD_CHUNK = 1L << 0; ++ /** ++ * This flag requires the entity being teleported to be a root vehicle. ++ * Thus, if you want to teleport a non-root vehicle, you must dismount ++ * the target entity before calling teleport, otherwise the ++ * teleport will be refused. ++ */ ++ public static final long TELEPORT_FLAG_TELEPORT_PASSENGERS = 1L << 1; ++ /** ++ * The flag will dismount any passengers and dismout from the current vehicle ++ * to teleport if and only if dismounting would result in the teleport being allowed. ++ */ ++ public static final long TELEPORT_FLAG_UNMOUNT = 1L << 2; ++ ++ protected void placeSingleSync(ServerLevel originWorld, ServerLevel destination, EntityTreeNode treeNode, long teleportFlags) { ++ destination.addDuringTeleport(this); ++ } ++ ++ protected final void placeInAsync(ServerLevel originWorld, ServerLevel destination, long teleportFlags, ++ EntityTreeNode passengerTree, java.util.function.Consumer teleportComplete) { ++ Vec3 pos = this.position(); ++ ChunkPos posChunk = new ChunkPos( ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos) ++ ); ++ ++ // ensure the region is always ticking in case of a shutdown ++ // otherwise, the shutdown will not be able to complete the shutdown as it requires a ticking region ++ Long teleportHoldId = Long.valueOf(TELEPORT_HOLD_TICKET_GEN.getAndIncrement()); ++ originWorld.chunkSource.addTicketAtLevel( ++ TicketType.TELEPORT_HOLD_TICKET, posChunk, ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ teleportHoldId ++ ); ++ final ServerLevel.PendingTeleport pendingTeleport = new ServerLevel.PendingTeleport(passengerTree, pos); ++ destination.pushPendingTeleport(pendingTeleport); ++ ++ Runnable scheduleEntityJoin = () -> { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( ++ destination, ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos), ++ () -> { ++ if (!destination.removePendingTeleport(pendingTeleport)) { ++ // shutdown logic placed the entity already, and we are shutting down - do nothing to ensure ++ // we do not produce any errors here ++ return; ++ } ++ originWorld.chunkSource.removeTicketAtLevel( ++ TicketType.TELEPORT_HOLD_TICKET, posChunk, ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ teleportHoldId ++ ); ++ List fullTree = passengerTree.getFullTree(); ++ for (EntityTreeNode node : fullTree) { ++ node.root.placeSingleSync(originWorld, destination, node, teleportFlags); ++ } ++ ++ // restore passenger tree ++ passengerTree.restore(); ++ passengerTree.adjustRiders(true); ++ ++ // invoke post dimension change now ++ for (EntityTreeNode node : fullTree) { ++ node.root.postChangeDimension(); ++ } ++ ++ if (teleportComplete != null) { ++ teleportComplete.accept(Entity.this); ++ } ++ } ++ ); ++ }; ++ ++ if ((teleportFlags & TELEPORT_FLAG_LOAD_CHUNK) != 0L) { ++ destination.loadChunksForMoveAsync( ++ this.getBoundingBox(), ca.spottedleaf.concurrentutil.util.Priority.HIGHER, ++ (chunkList) -> { ++ for (net.minecraft.world.level.chunk.ChunkAccess chunk : chunkList) { ++ destination.chunkSource.addTicketAtLevel( ++ TicketType.POST_TELEPORT, chunk.getPos(), ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.FULL_LOADED_TICKET_LEVEL, ++ Integer.valueOf(Entity.this.getId()) ++ ); ++ } ++ scheduleEntityJoin.run(); ++ } ++ ); ++ } else { ++ scheduleEntityJoin.run(); ++ } ++ } ++ ++ protected boolean canTeleportAsync() { ++ return !this.hasNullCallback() && !this.isRemoved() && this.isAlive() && (!(this instanceof net.minecraft.world.entity.LivingEntity livingEntity) || !livingEntity.isSleeping()); ++ } ++ ++ // Mojang for whatever reason has started storing positions to cache certain physics properties that entities collide with ++ // As usual though, they don't properly do anything to prevent serious desync with respect to the current entity position ++ // We add additional logic to reset these before teleporting to prevent issues with them possibly tripping thread checks. ++ protected void resetStoredPositions() { ++ this.mainSupportingBlockPos = Optional.empty(); ++ // this is copied from teleportSetPosition ++ this.movementThisTick.clear(); ++ } ++ ++ 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.moveTo(pos.x, pos.y, pos.z); ++ this.setOldPosAndRot(); ++ this.resetStoredPositions(); ++ } ++ ++ protected final void transform(TeleportTransition telpeort) { ++ PositionMoveRotation move = PositionMoveRotation.calculateAbsolute( ++ PositionMoveRotation.of(this), PositionMoveRotation.of(telpeort), telpeort.relatives() ++ ); ++ this.transform( ++ move.position(), Float.valueOf(move.yRot()), Float.valueOf(move.xRot()), move.deltaMovement() ++ ); ++ } ++ ++ protected void transform(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); ++ } ++ if (pos != null) { ++ this.setPosRaw(pos.x, pos.y, pos.z); ++ } ++ this.setOldPosAndRot(); ++ } ++ ++ protected final Entity transformForAsyncTeleport(TeleportTransition telpeort) { ++ PositionMoveRotation move = PositionMoveRotation.calculateAbsolute( ++ PositionMoveRotation.of(this), PositionMoveRotation.of(telpeort), telpeort.relatives() ++ ); ++ return this.transformForAsyncTeleport( ++ telpeort.newLevel(), telpeort.position(), Float.valueOf(move.yRot()), Float.valueOf(move.xRot()), move.deltaMovement() ++ ); ++ } ++ ++ protected Entity transformForAsyncTeleport(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity) { ++ this.removeAfterChangingDimensions(); // remove before so that any CBEntity#getHandle call affects this entity before copying ++ ++ Entity copy = this.getType().create(destination, EntitySpawnReason.DIMENSION_TRAVEL); ++ copy.restoreFrom(this); ++ copy.transform(pos, yaw, pitch, velocity); ++ // vanilla code used to call remove _after_ copying, and some stuff is required to be after copy - so add hook here ++ // for example, clearing of inventory after switching dimensions ++ this.postRemoveAfterChangingDimensions(); ++ ++ return copy; ++ } ++ ++ public final boolean teleportAsync(TeleportTransition teleportTarget, long teleportFlags, ++ java.util.function.Consumer teleportComplete) { ++ PositionMoveRotation move = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); ++ ++ return this.teleportAsync( ++ teleportTarget.newLevel(), move.position(), Float.valueOf(move.yRot()), Float.valueOf(move.xRot()), move.deltaMovement(), ++ teleportTarget.cause(), teleportFlags, teleportComplete ++ ); ++ } ++ ++ public final boolean teleportAsync(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity, ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, long teleportFlags, ++ java.util.function.Consumer teleportComplete) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot teleport entity async"); ++ ++ if (!ServerLevel.isInSpawnableBounds(new BlockPos(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockY(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockZ(pos)))) { ++ return false; ++ } ++ ++ if (!this.canTeleportAsync()) { ++ return false; ++ } ++ this.getBukkitEntity(); // force bukkit entity to be created before TPing ++ if ((teleportFlags & TELEPORT_FLAG_UNMOUNT) == 0L) { ++ for (Entity entity : this.getIndirectPassengers()) { ++ if (!entity.canTeleportAsync()) { ++ return false; ++ } ++ entity.getBukkitEntity(); // force bukkit entity to be created before TPing ++ } ++ } else { ++ this.unRide(); ++ } ++ ++ if ((teleportFlags & TELEPORT_FLAG_TELEPORT_PASSENGERS) != 0L) { ++ if (this.isPassenger()) { ++ return false; ++ } ++ } else { ++ if (this.isVehicle() || this.isPassenger()) { ++ return false; ++ } ++ } ++ ++ // TODO any events that can modify go HERE ++ ++ // check for same region ++ if (destination == this.level()) { ++ Vec3 currPos = this.position(); ++ if ( ++ destination.regioniser.getRegionAtUnsynchronised( ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(currPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(currPos) ++ ) == destination.regioniser.getRegionAtUnsynchronised( ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos) ++ ) ++ ) { ++ boolean hasPassengers = !this.passengers.isEmpty(); ++ EntityTreeNode passengerTree = this.detachPassengers(); ++ ++ if (hasPassengers) { ++ // Note: The client does not accept position updates for controlled entities. So, we must ++ // perform a lot of tracker updates here to make it all work out. ++ ++ // first, clear the tracker ++ passengerTree.clearTracker(); ++ } ++ ++ for (EntityTreeNode entity : passengerTree.getFullTree()) { ++ entity.root.teleportSyncSameRegion(pos, yaw, pitch, velocity); ++ } ++ ++ if (hasPassengers) { ++ passengerTree.restore(); ++ // re-add to the tracker once the tree is restored ++ passengerTree.addTracker(); ++ ++ // adjust entities to final position ++ passengerTree.adjustRiders(true); ++ ++ // the tracker clear/add logic is only used in the same region, as the other logic ++ // performs add/remove from world logic which will also perform add/remove tracker logic ++ } ++ ++ if (teleportComplete != null) { ++ teleportComplete.accept(this); ++ } ++ return true; ++ } ++ } ++ ++ EntityTreeNode passengerTree = this.detachPassengers(); ++ List fullPassengerTree = passengerTree.getFullTree(); ++ ServerLevel originWorld = (ServerLevel)this.level; ++ ++ for (EntityTreeNode node : fullPassengerTree) { ++ node.root.preChangeDimension(); ++ } ++ ++ for (EntityTreeNode node : fullPassengerTree) { ++ node.root = node.root.transformForAsyncTeleport(destination, pos, yaw, pitch, velocity); ++ } ++ ++ passengerTree.root.placeInAsync(originWorld, destination, teleportFlags, passengerTree, teleportComplete); ++ ++ return true; ++ } ++ ++ public void preChangeDimension() { ++ if (this instanceof Leashable leashable) { ++ leashable.dropLeash(); ++ } ++ } ++ ++ public void postChangeDimension() { ++ this.resetStoredPositions(); ++ } ++ ++ protected static enum PortalType { ++ NETHER, END; ++ } ++ ++ public boolean endPortalLogicAsync(BlockPos portalPos) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async"); ++ ++ ServerLevel destination = this.getServer().getLevel(this.level().getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END ? Level.OVERWORLD : Level.END); ++ if (destination == null) { ++ // wat ++ return false; ++ } ++ ++ return this.portalToAsync(destination, portalPos, true, PortalType.END, null); ++ } ++ ++ public boolean netherPortalLogicAsync(BlockPos portalPos) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async"); ++ ++ ServerLevel destination = this.getServer().getLevel(this.level().getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER); ++ if (destination == null) { ++ // wat ++ return false; ++ } ++ ++ return this.portalToAsync(destination, portalPos, true, PortalType.NETHER, null); ++ } ++ ++ private static final java.util.concurrent.atomic.AtomicLong CREATE_PORTAL_DOUBLE_CHECK = new java.util.concurrent.atomic.AtomicLong(); ++ private static final java.util.concurrent.atomic.AtomicLong TELEPORT_HOLD_TICKET_GEN = new java.util.concurrent.atomic.AtomicLong(); ++ ++ // To simplify portal logic, in region threading both players ++ // and non-player entities will create portals. By guaranteeing ++ // that the teleportation can take place, we can simply ++ // remove the entity, find/create the portal, and place async. ++ // If we have to worry about whether the entity may not teleport, ++ // we need to first search, then report back, ... ++ protected void findOrCreatePortalAsync(ServerLevel origin, BlockPos originPortal, ServerLevel destination, PortalType type, ++ ca.spottedleaf.concurrentutil.completable.CallbackCompletable portalInfoCompletable) { ++ switch (type) { ++ // end portal logic is quite simple, the spawn in the end is fixed and when returning to the overworld ++ // we just select the spawn position ++ case END: { ++ if (destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) { ++ BlockPos targetPos = ServerLevel.END_SPAWN_POINT; ++ // need to load chunks so we can create the platform ++ destination.moonrise$loadChunksAsync( ++ targetPos, 16, // load 16 blocks to be safe from block physics ++ ca.spottedleaf.concurrentutil.util.Priority.HIGH, ++ (chunks) -> { ++ net.minecraft.world.level.levelgen.feature.EndPlatformFeature.createEndPlatform(destination, targetPos.below(), true, null); ++ ++ // the portal obsidian is placed at targetPos.y - 2, so if we want to place the entity ++ // on the obsidian, we need to spawn at targetPos.y - 1 ++ portalInfoCompletable.complete( ++ new net.minecraft.world.level.portal.TeleportTransition( ++ destination, Vec3.atBottomCenterOf(targetPos.below()), Vec3.ZERO, Direction.WEST.toYRot(), 0.0f, ++ Relative.union(Relative.DELTA, Set.of(Relative.X_ROT)), ++ TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET), ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL ++ ) ++ ); ++ } ++ ); ++ } else { ++ BlockPos spawnPos = destination.getSharedSpawnPos(); ++ // need to load chunk for heightmap ++ destination.moonrise$loadChunksAsync( ++ spawnPos, 0, ++ ca.spottedleaf.concurrentutil.util.Priority.HIGH, ++ (chunks) -> { ++ BlockPos adjustedSpawn = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, spawnPos); ++ ++ // done ++ portalInfoCompletable.complete( ++ new net.minecraft.world.level.portal.TeleportTransition( ++ destination, Vec3.atBottomCenterOf(adjustedSpawn), Vec3.ZERO, 0.0f, 0.0f, ++ Relative.union(Relative.DELTA, Relative.ROTATION), ++ TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET), ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL ++ ) ++ ); ++ } ++ ); ++ } ++ ++ break; ++ } ++ // for the nether logic, we need to first load the chunks in radius to empty (so that POI is created) ++ // then we can search for an existing portal using the POI routines ++ // if we don't find a portal, then we bring the chunks in the create radius to full and ++ // create it ++ case NETHER: { ++ // hoisted from the create fallback, so that we can avoid the sync load later if we need it ++ BlockState originalPortalBlock = origin.getBlockStateIfLoaded(originPortal); ++ Direction.Axis originalPortalDirection = originalPortalBlock == null ? Direction.Axis.X : ++ originalPortalBlock.getOptionalValue(net.minecraft.world.level.block.NetherPortalBlock.AXIS).orElse(Direction.Axis.X); ++ BlockUtil.FoundRectangle originalPortalRectangle = ++ originalPortalBlock == null || !originalPortalBlock.hasProperty(net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_AXIS) ++ ? null ++ : BlockUtil.getLargestRectangleAround( ++ originPortal, originalPortalDirection, 21, Direction.Axis.Y, 21, ++ (blockpos) -> { ++ return origin.getBlockStateFromEmptyChunkIfLoaded(blockpos) == originalPortalBlock; ++ } ++ ); ++ ++ boolean destinationIsNether = destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER; ++ ++ int portalSearchRadius = origin.paperConfig().environment.portalSearchVanillaDimensionScaling && destinationIsNether ? ++ (int)(destination.paperConfig().environment.portalSearchRadius / destination.dimensionType().coordinateScale()) : ++ destination.paperConfig().environment.portalSearchRadius; ++ int portalCreateRadius = destination.paperConfig().environment.portalCreateRadius; ++ ++ WorldBorder destinationBorder = destination.getWorldBorder(); ++ double dimensionScale = net.minecraft.world.level.dimension.DimensionType.getTeleportationScale(origin.dimensionType(), destination.dimensionType()); ++ BlockPos targetPos = destination.getWorldBorder().clampToBounds(this.getX() * dimensionScale, this.getY(), this.getZ() * dimensionScale); ++ ++ ca.spottedleaf.concurrentutil.completable.CallbackCompletable portalFound ++ = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); ++ ++ // post portal find/create logic ++ portalFound.addWaiter( ++ (BlockUtil.FoundRectangle portal, Throwable thr) -> { ++ // no portal could be created ++ if (portal == null) { ++ portalInfoCompletable.complete( ++ new TeleportTransition(destination, Vec3.atCenterOf(targetPos), Vec3.ZERO, ++ 90.0f, 0.0f, ++ TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET)) ++ ); ++ return; ++ } ++ ++ Vec3 relativePos = originalPortalRectangle == null ? ++ new Vec3(0.5, 0.0, 0.0) : ++ Entity.this.getRelativePortalPosition(originalPortalDirection, originalPortalRectangle); ++ ++ portalInfoCompletable.complete( ++ net.minecraft.world.level.block.NetherPortalBlock.createDimensionTransition( ++ destination, portal, originalPortalDirection, relativePos, ++ Entity.this, TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET) ++ ) ++ ); ++ } ++ ); ++ ++ // kick off search for existing portal or creation ++ destination.moonrise$loadChunksAsync( ++ // add 32 so that the final search for a portal frame doesn't load any chunks ++ targetPos, portalSearchRadius + 32, ++ net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY, ++ ca.spottedleaf.concurrentutil.util.Priority.HIGH, ++ (chunks) -> { ++ BlockUtil.FoundRectangle portal = ++ net.minecraft.world.level.block.NetherPortalBlock.findPortalAround(destination, targetPos, destinationBorder, portalSearchRadius); ++ if (portal != null) { ++ portalFound.complete(portal); ++ return; ++ } ++ ++ // add tickets so that we can re-search for a portal once the chunks are loaded ++ Long ticketId = Long.valueOf(CREATE_PORTAL_DOUBLE_CHECK.getAndIncrement()); ++ for (net.minecraft.world.level.chunk.ChunkAccess chunk : chunks) { ++ destination.chunkSource.addTicketAtLevel( ++ TicketType.NETHER_PORTAL_DOUBLE_CHECK, chunk.getPos(), ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ ticketId ++ ); ++ } ++ ++ // no portal found - create one ++ destination.moonrise$loadChunksAsync( ++ targetPos, portalCreateRadius + 32, ++ ca.spottedleaf.concurrentutil.util.Priority.HIGH, ++ (chunks2) -> { ++ // don't need the tickets anymore ++ // note: we expect removeTicketsAtLevel to add an unknown ticket for us automatically ++ // if the ticket level were to decrease ++ for (net.minecraft.world.level.chunk.ChunkAccess chunk : chunks) { ++ destination.chunkSource.removeTicketAtLevel( ++ TicketType.NETHER_PORTAL_DOUBLE_CHECK, chunk.getPos(), ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ ticketId ++ ); ++ } ++ ++ // when two entities portal at the same time, it is possible that both entities reach this ++ // part of the code - and create a double portal ++ // to fix this, we just issue another search to try and see if another entity created ++ // a portal nearby ++ BlockUtil.FoundRectangle existingTryAgain = ++ net.minecraft.world.level.block.NetherPortalBlock.findPortalAround(destination, targetPos, destinationBorder, portalSearchRadius); ++ if (existingTryAgain != null) { ++ portalFound.complete(existingTryAgain); ++ return; ++ } ++ ++ // we do not have the correct entity reference here ++ BlockUtil.FoundRectangle createdPortal = ++ destination.getPortalForcer().createPortal(targetPos, originalPortalDirection, null, portalCreateRadius).orElse(null); ++ // if it wasn't created, passing null is expected here ++ portalFound.complete(createdPortal); ++ } ++ ); ++ } ++ ); ++ break; ++ } ++ default: { ++ throw new IllegalStateException("Unknown portal type " + type); ++ } ++ } ++ } ++ ++ public boolean canPortalAsync(ServerLevel to, boolean considerPassengers) { ++ return this.canPortalAsync(to, considerPassengers, false); ++ } ++ ++ protected boolean canPortalAsync(ServerLevel to, boolean considerPassengers, boolean skipPassengerCheck) { ++ if (considerPassengers) { ++ if (!skipPassengerCheck && this.isPassenger()) { ++ return false; ++ } ++ } else { ++ if (this.isVehicle() || (!skipPassengerCheck && this.isPassenger())) { ++ return false; ++ } ++ } ++ this.getBukkitEntity(); // force bukkit entity to be created before TPing ++ if (!this.canTeleportAsync()) { ++ return false; ++ } ++ if (considerPassengers) { ++ for (Entity entity : this.passengers) { ++ if (!entity.canPortalAsync(to, true, true)) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ protected void prePortalLogic(ServerLevel origin, ServerLevel destination, PortalType type) { ++ ++ } ++ ++ protected boolean portalToAsync(ServerLevel destination, BlockPos portalPos, boolean takePassengers, ++ PortalType type, java.util.function.Consumer teleportComplete) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async"); ++ if (!this.canPortalAsync(destination, takePassengers)) { ++ return false; ++ } ++ ++ Vec3 initialPosition = this.position(); ++ ChunkPos initialPositionChunk = new ChunkPos( ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(initialPosition), ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(initialPosition) ++ ); ++ ++ // first, remove entity/passengers from world ++ EntityTreeNode passengerTree = this.detachPassengers(); ++ List fullPassengerTree = passengerTree.getFullTree(); ++ ServerLevel originWorld = (ServerLevel)this.level; ++ ++ for (EntityTreeNode node : fullPassengerTree) { ++ node.root.preChangeDimension(); ++ node.root.prePortalLogic(originWorld, destination, type); ++ } ++ ++ for (EntityTreeNode node : fullPassengerTree) { ++ // we will update pos/rot/speed later ++ node.root = node.root.transformForAsyncTeleport(destination, null, null, null, null); ++ // set portal cooldown ++ node.root.setPortalCooldown(); ++ } ++ ++ // ensure the region is always ticking in case of a shutdown ++ // otherwise, the shutdown will not be able to complete the shutdown as it requires a ticking region ++ Long teleportHoldId = Long.valueOf(TELEPORT_HOLD_TICKET_GEN.getAndIncrement()); ++ originWorld.chunkSource.addTicketAtLevel( ++ TicketType.TELEPORT_HOLD_TICKET, initialPositionChunk, ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ teleportHoldId ++ ); ++ ++ ServerLevel.PendingTeleport beforeFindDestination = new ServerLevel.PendingTeleport(passengerTree, initialPosition); ++ originWorld.pushPendingTeleport(beforeFindDestination); ++ ++ ca.spottedleaf.concurrentutil.completable.CallbackCompletable portalInfoCompletable ++ = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); ++ ++ portalInfoCompletable.addWaiter((TeleportTransition info, Throwable throwable) -> { ++ if (!originWorld.removePendingTeleport(beforeFindDestination)) { ++ // the shutdown thread has placed us back into the origin world at the original position ++ // we just have to abandon this teleport to prevent duplication ++ return; ++ } ++ originWorld.chunkSource.removeTicketAtLevel( ++ TicketType.TELEPORT_HOLD_TICKET, initialPositionChunk, ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ teleportHoldId ++ ); ++ // adjust passenger tree to final pos/rot/speed ++ for (EntityTreeNode node : fullPassengerTree) { ++ node.root.transform(info); ++ } ++ ++ // place ++ passengerTree.root.placeInAsync( ++ originWorld, destination, Entity.TELEPORT_FLAG_LOAD_CHUNK | (takePassengers ? Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS : 0L), ++ passengerTree, ++ (Entity teleported) -> { ++ if (info.postTeleportTransition() != null) { ++ info.postTeleportTransition().onTransition(teleported); ++ } ++ ++ if (teleportComplete != null) { ++ teleportComplete.accept(teleported); ++ } ++ } ++ ); ++ }); ++ ++ ++ passengerTree.root.findOrCreatePortalAsync(originWorld, portalPos, destination, type, portalInfoCompletable); ++ ++ return true; ++ } ++ // Folia end - region threading ++ + @Nullable + public Entity teleport(TeleportTransition teleportTransition) { ++ // Folia start - region threading ++ if (true) { ++ throw new UnsupportedOperationException("Must use teleportAsync while in region threading"); ++ } ++ // Folia end - region threading + // Paper start - Fix item duplication and teleport issues + if ((!this.isAlive() || !this.valid) && (teleportTransition.newLevel() != this.level)) { + LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTransition.newLevel() + ":" + teleportTransition.position(), new Throwable()); +@@ -3907,6 +4716,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + ++ // Folia start - region threading - move inventory clearing until after the dimension change ++ protected void postRemoveAfterChangingDimensions() { ++ ++ } ++ // Folia end - region threading - move inventory clearing until after the dimension change ++ + protected void removeAfterChangingDimensions() { + this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause + if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed +@@ -4242,6 +5057,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void startSeenByPlayer(ServerPlayer serverPlayer) { ++ // Folia start - region threading ++ if (serverPlayer.getCamera() == this) { ++ // set camera again ++ serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCameraPacket(this)); ++ } ++ // Folia end - region threading + } + + public void stopSeenByPlayer(ServerPlayer serverPlayer) { +@@ -4251,6 +5072,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + new io.papermc.paper.event.player.PlayerUntrackEntityEvent(serverPlayer.getBukkitEntity(), this.getBukkitEntity()).callEvent(); + } + // Paper end - entity tracking events ++ // Folia start - region threading ++ if (serverPlayer.getCamera() == this) { ++ // unset camera, the player tick method should TP us close enough again to invoke startSeenByPlayer ++ serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCameraPacket(serverPlayer)); ++ } ++ // Folia end - region threading + } + + public float rotate(Rotation transformRotation) { +@@ -4786,7 +5613,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + // Paper end - Fix MC-4 +- if (this.position.x != x || this.position.y != y || this.position.z != z) { ++ boolean posChanged = this.position.x != x || this.position.y != y || this.position.z != z; ++ if (posChanged) { // Folia - region threading + synchronized (this.posLock) { // Paper - detailed watchdog information + this.position = new Vec3(x, y, z); + } // Paper - detailed watchdog information +@@ -4805,7 +5633,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB + // hanging has its own special logic +- if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { ++ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || posChanged)) { + this.setBoundingBox(this.makeBoundingBox()); + } + // Paper end - Block invalid positions and bounding box +@@ -4889,6 +5717,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return this.removalReason != null; + } + ++ // Folia start - region threading ++ public final boolean hasNullCallback() { ++ return this.levelCallback == EntityInLevelCallback.NULL; ++ } ++ // Folia end - region threading ++ + @Nullable + public Entity.RemovalReason getRemovalReason() { + return this.removalReason; +@@ -4911,6 +5745,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); + // CraftBukkit end + final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers ++ // Folia start - region threading ++ this.preRemove(removalReason); ++ // Folia end - region threading + if (this.removalReason == null) { + this.removalReason = removalReason; + } +@@ -4934,6 +5771,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.removalReason = null; + } + ++ // Folia start - region threading ++ protected void preRemove(Entity.RemovalReason reason) {} ++ // Folia end - region threading ++ + // Paper start - Folia schedulers + /** + * Invoked only when the entity is truly removed from the server, never to be added to any world. +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index dd07daf3eb4a21a0893229d51ddd52de892e3e4c..b9aecea2e3f05500991254c931fda3b06bebdcea 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -278,7 +278,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + private Optional lastClimbablePos = Optional.empty(); + @Nullable + private DamageSource lastDamageSource; +- private long lastDamageStamp; ++ private long lastDamageStamp = Long.MIN_VALUE; // Folia - region threading + protected int autoSpinAttackTicks; + protected float autoSpinAttackDmg; + @Nullable +@@ -307,6 +307,26 @@ public abstract class LivingEntity extends Entity implements Attackable { + return this.getYHeadRot(); + } + // CraftBukkit end ++ // Folia start - region threading ++ @Override ++ public void updateTicks(long fromTickOffset, long fromRedstoneTimeOffset) { ++ super.updateTicks(fromTickOffset, fromRedstoneTimeOffset); ++ if (this.lastDamageStamp != Long.MIN_VALUE) { ++ this.lastDamageStamp += fromRedstoneTimeOffset; ++ } ++ } ++ ++ @Override ++ public boolean canBeSpectated() { ++ return super.canBeSpectated() && this.getHealth() > 0.0F; ++ } ++ ++ @Override ++ protected void resetStoredPositions() { ++ super.resetStoredPositions(); ++ this.lastClimbablePos = Optional.empty(); ++ } ++ // Folia end - region threading + + protected LivingEntity(EntityType entityType, Level level) { + super(entityType, level); +@@ -528,7 +548,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + + if (this.isDeadOrDying() && this.level().shouldTickDeath(this)) { + this.tickDeath(); +- } ++ } else { this.broadcastedDeath = false; } // Folia - region threading + + if (this.lastHurtByPlayerTime > 0) { + this.lastHurtByPlayerTime--; +@@ -611,11 +631,14 @@ public abstract class LivingEntity extends Entity implements Attackable { + return true; + } + ++ public boolean broadcastedDeath = false; // Folia - region threading + protected void tickDeath() { + this.deathTime++; + if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) { + this.level().broadcastEntityEvent(this, (byte)60); +- this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause ++ this.broadcastedDeath = true; // Folia - region threading - death has been broadcasted ++ if (!(this instanceof ServerPlayer)) this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause // Folia - region threading - don't remove, we want the tick scheduler to be running ++ if ((this instanceof ServerPlayer)) this.unRide(); // Folia - region threading - unmount player when dead + } + } + +@@ -851,9 +874,9 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + this.hurtTime = compound.getShort("HurtTime"); +- this.deathTime = compound.getShort("DeathTime"); ++ this.deathTime = compound.getShort("DeathTime"); this.broadcastedDeath = false; // Folia - region threading + this.lastHurtByMobTimestamp = compound.getInt("HurtByTimestamp"); +- if (compound.contains("Team", 8)) { ++ if (false && compound.contains("Team", 8)) { // Folia start - region threading + String string = compound.getString("Team"); + Scoreboard scoreboard = this.level().getScoreboard(); + PlayerTeam playerTeam = scoreboard.getPlayerTeam(string); +@@ -1115,6 +1138,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) { + // Paper end - Don't fire sync event during generation + // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API ++ if (!this.hasNullCallback()) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add effects to entities asynchronously"); // Folia - region threading + if (this.isTickingEffects) { + this.effectsToProcess.add(new ProcessableEffect(effectInstance, cause)); + return true; +@@ -1502,7 +1526,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + boolean flag2 = !flag; // CraftBukkit - Ensure to return false if damage is blocked + if (flag2) { + this.lastDamageSource = damageSource; +- this.lastDamageStamp = this.level().getGameTime(); ++ this.lastDamageStamp = this.level().getRedstoneGameTime(); // Folia - region threading + + for (MobEffectInstance mobEffectInstance : this.getActiveEffects()) { + mobEffectInstance.onMobHurt(level, this, damageSource, amount); +@@ -1629,7 +1653,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + + @Nullable + public DamageSource getLastDamageSource() { +- if (this.level().getGameTime() - this.lastDamageStamp > 40L) { ++ if (this.level().getRedstoneGameTime() - this.lastDamageStamp > 40L || this.lastDamageStamp == Long.MIN_VALUE) { // Folia - region threading + this.lastDamageSource = null; + } + +@@ -2420,10 +2444,10 @@ public abstract class LivingEntity extends Entity implements Attackable { + + @Nullable + public LivingEntity getKillCredit() { +- if (this.lastHurtByPlayer != null) { ++ if (this.lastHurtByPlayer != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.lastHurtByPlayer)) { // Folia - region threading + return this.lastHurtByPlayer; + } else { +- return this.lastHurtByMob != null ? this.lastHurtByMob : null; ++ return this.lastHurtByMob != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.lastHurtByMob) ? this.lastHurtByMob : null; // Folia - region threading + } + } + +@@ -2502,7 +2526,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + this.lastDamageSource = damageSource; +- this.lastDamageStamp = this.level().getGameTime(); ++ this.lastDamageStamp = this.level().getRedstoneGameTime(); // Folia - region threading + } + + @Override +@@ -3479,7 +3503,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.pushEntities(); + profilerFiller.pop(); + // Paper start - Add EntityMoveEvent +- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof Player)) { ++ if (((ServerLevel) this.level()).getCurrentWorldData().hasEntityMoveEvent && !(this instanceof Player)) { // Folia - region threading + if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { + Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); + Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); +@@ -4152,7 +4176,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + boolean flag = false; + BlockPos blockPos = BlockPos.containing(x, y, z); + Level level = this.level(); +- if (level.hasChunkAt(blockPos)) { ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((ServerLevel)level, blockPos) && level.hasChunkAt(blockPos)) { // Folia - region threading + boolean flag1 = false; + + while (!flag1 && blockPos.getY() > level.getMinY()) { +@@ -4314,6 +4338,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); +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index 1ed07fd23985a6bf8cf8300f74c92b7531a79fc6..6394b0899095b047ca9266135fc44aa0c32467cf 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -254,8 +254,20 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + @Nullable + @Override + public LivingEntity getTarget() { ++ // Folia start - region threading ++ if (this.target != null && (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.target) || this.target.isRemoved())) { ++ this.target = null; ++ return null; ++ } ++ // Folia end - region threading ++ return this.target; ++ } ++ ++ // Folia start - region threading ++ public LivingEntity getTargetRaw() { + return this.target; + } ++ // Folia end - region threading + + @Nullable + protected final LivingEntity getTargetFromBrain() { +@@ -268,7 +280,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + } + + public boolean setTarget(LivingEntity target, EntityTargetEvent.TargetReason reason, boolean fireEvent) { +- if (this.getTarget() == target) { ++ if (this.getTargetRaw() == target) { // Folia - region threading + return false; + } + if (fireEvent) { +@@ -1663,12 +1675,26 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + @Override + protected void removeAfterChangingDimensions() { + super.removeAfterChangingDimensions(); ++ // Folia start - region threading - move inventory clearing until after the dimension change - move into postRemoveAfterChangingDimensions ++// this.getAllSlots().forEach(itemStack -> { ++// if (!itemStack.isEmpty()) { ++// itemStack.setCount(0); ++// } ++// }); ++ // Folia end - region threading - move inventory clearing until after the dimension change - move into postRemoveAfterChangingDimensions ++ } ++ ++ // Folia start - region threading ++ @Override ++ protected void postRemoveAfterChangingDimensions() { ++ super.postRemoveAfterChangingDimensions(); + this.getAllSlots().forEach(itemStack -> { + if (!itemStack.isEmpty()) { + itemStack.setCount(0); + } + }); + } ++ // Folia end - region threading + + @Nullable + @Override +diff --git a/net/minecraft/world/entity/PortalProcessor.java b/net/minecraft/world/entity/PortalProcessor.java +index 88b07fbb96b20124777889830afa480673629d43..46d989aef0eceebd98bfd93999153319de77a8a0 100644 +--- a/net/minecraft/world/entity/PortalProcessor.java ++++ b/net/minecraft/world/entity/PortalProcessor.java +@@ -33,6 +33,12 @@ public class PortalProcessor { + return this.portal.getPortalDestination(level, entity, this.entryPosition); + } + ++ // Folia start - region threading ++ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget) { ++ return this.portal.portalAsync(sourceWorld, portalTarget, this.entryPosition); ++ } ++ // Folia end - region threading ++ + public Portal.Transition getPortalLocalTransition() { + return this.portal.getLocalTransition(); + } +diff --git a/net/minecraft/world/entity/TamableAnimal.java b/net/minecraft/world/entity/TamableAnimal.java +index fc3ba135ae502aaa5c3a9fa3297bf7b12c1ab063..b0b1e38f2b70ed548790fd0445db4541c34b0f34 100644 +--- a/net/minecraft/world/entity/TamableAnimal.java ++++ b/net/minecraft/world/entity/TamableAnimal.java +@@ -263,6 +263,11 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + public void tryToTeleportToOwner() { + LivingEntity owner = this.getOwner(); + if (owner != null) { ++ // Folia start - region threading ++ if (owner.isRemoved() || owner.level() != this.level()) { ++ return; ++ } ++ // Folia end - region threading + this.teleportToAroundBlockPos(owner.blockPosition()); + } + } +@@ -295,7 +300,22 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + return false; + } + org.bukkit.Location to = event.getTo(); +- this.moveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); ++ // Folia start - region threading - can't teleport here, we may be removed by teleport logic - delay until next tick ++ // also, use teleportAsync so that crossing region boundaries will not blow up ++ org.bukkit.Location finalTo = to; ++ Level sourceWorld = this.level(); ++ this.getBukkitEntity().taskScheduler.schedule((TamableAnimal nmsEntity) -> { ++ if (nmsEntity.level() == sourceWorld) { ++ nmsEntity.teleportAsync( ++ (net.minecraft.server.level.ServerLevel)nmsEntity.level(), ++ new net.minecraft.world.phys.Vec3(finalTo.getX(), finalTo.getY(), finalTo.getZ()), ++ Float.valueOf(finalTo.getYaw()), Float.valueOf(finalTo.getPitch()), ++ net.minecraft.world.phys.Vec3.ZERO, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN, Entity.TELEPORT_FLAG_LOAD_CHUNK, ++ null ++ ); ++ } ++ }, null, 1L); ++ // Folia end - region threading - can't teleport here, we may be removed by teleport logic - delay until next tick + // CraftBukkit end + this.navigation.stop(); + return true; +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java +index 450396468b23fd90cb8036dbbdd0927051f907af..65b2b3ece213d901cdd585093e2fafcd2ef4a7cd 100644 +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java +@@ -425,9 +425,17 @@ public class Brain { + } + + public void stopAll(ServerLevel level, E owner) { ++ // Folia start - region threading ++ List> behaviors = this.getRunningBehaviors(); ++ if (behaviors.isEmpty()) { ++ // avoid calling getGameTime, as this may be called while portalling an entity - which will cause ++ // the world data retrieval to fail ++ return; ++ } ++ // Folia end - region threading + long gameTime = owner.level().getGameTime(); + +- for (BehaviorControl behaviorControl : this.getRunningBehaviors()) { ++ for (BehaviorControl behaviorControl : behaviors) { // Folia - region threading + behaviorControl.doStop(level, owner, gameTime); + } + } +diff --git a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java +index 3614551856c594f3c0cfee984fcf03fad672b007..f37aee679451dfcaf945aa7a3f668229bff03c3c 100644 +--- a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java ++++ b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java +@@ -46,12 +46,14 @@ public class GoToPotentialJobSite extends Behavior { + BlockPos blockPos = globalPos.pos(); + ServerLevel level1 = level.getServer().getLevel(globalPos.dimension()); + if (level1 != null) { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(level1, blockPos.getX() >> 4, blockPos.getZ() >> 4, () -> { // Folia - region threading + PoiManager poiManager = level1.getPoiManager(); + if (poiManager.exists(blockPos, holder -> true)) { + poiManager.release(blockPos); + } + + DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ }); // Folia - region threading + } + }); + entity.getBrain().eraseMemory(MemoryModuleType.POTENTIAL_JOB_SITE); +diff --git a/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java b/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java +index b11a16db0ea22ebd68db9c96e0ba0939b6596caf..9f5b5ad2fe08f25f4c922ae641d1a2e8bce18ccb 100644 +--- a/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java ++++ b/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java +@@ -19,6 +19,11 @@ public class PoiCompetitorScan { + instance, + (jobSite, nearestLivingEntities) -> (level, villager, gameTime) -> { + GlobalPos globalPos = instance.get(jobSite); ++ // Folia start - region threading ++ if (globalPos.dimension() != level.dimension() || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, globalPos.pos())) { ++ return true; ++ } ++ // Folia end - region threading + level.getPoiManager() + .getType(globalPos.pos()) + .ifPresent( +diff --git a/net/minecraft/world/entity/ai/behavior/YieldJobSite.java b/net/minecraft/world/entity/ai/behavior/YieldJobSite.java +index 37ad79e201e36a1a9520219e3faa4dcffa7b4dfd..4cf9bb7ef73fbeb400991c3d8ff59141a0ea2575 100644 +--- a/net/minecraft/world/entity/ai/behavior/YieldJobSite.java ++++ b/net/minecraft/world/entity/ai/behavior/YieldJobSite.java +@@ -33,7 +33,13 @@ public class YieldJobSite { + } else if (villager.getVillagerData().getProfession() != VillagerProfession.NONE) { + return false; + } else { +- BlockPos blockPos = instance.get(potentialJobSite).pos(); ++ // Folia start - region threading ++ GlobalPos globalPos = instance.get(potentialJobSite); ++ BlockPos blockPos = globalPos.pos(); ++ if (globalPos.dimension() != level.dimension() || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, blockPos)) { ++ return true; ++ } ++ // Folia end - region threading + Optional> type = level.getPoiManager().getType(blockPos); + if (type.isEmpty()) { + return true; +diff --git a/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java b/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java +index dde287e823f906681e3addf03fa821c8786c9900..e5e3ce6eeb01ac4387eaee20d09ef469d8b3bc5e 100644 +--- a/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java ++++ b/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java +@@ -51,7 +51,7 @@ public class FollowOwnerGoal extends Goal { + public boolean canContinueToUse() { + return !this.navigation.isDone() + && !this.tamable.unableToMoveToOwner() +- && !(this.tamable.distanceToSqr(this.owner) <= this.stopDistance * this.stopDistance); ++ && !(this.owner.level() == this.tamable.level() && this.tamable.distanceToSqr(this.owner) <= this.stopDistance * this.stopDistance); // Folia - region threading - check level + } + + @Override +diff --git a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +index 045cfafb3afe8271d60852ae3c7cdcb039b44d4f..a24e964aff5623e3d7f2b79c87b6067f565458c2 100644 +--- a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java +@@ -42,6 +42,11 @@ public class GroundPathNavigation extends PathNavigation { + + @Override + public Path createPath(BlockPos pos, @javax.annotation.Nullable Entity entity, int accuracy) { // Paper - EntityPathfindEvent ++ // Folia start - region threading ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level, pos)) { ++ return null; ++ } ++ // Folia end - region threading + LevelChunk chunkNow = this.level.getChunkSource().getChunkNow(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())); + if (chunkNow == null) { + return null; +diff --git a/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index b44f2c49509d847817a78e9c4fb1499fb378054b..386580035e6789d6e668b924513ddfc81947a9b3 100644 +--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -96,11 +96,11 @@ public abstract class PathNavigation { + } + + public void recomputePath() { +- if (this.level.getGameTime() - this.timeLastRecompute > 20L) { ++ if (this.tick - this.timeLastRecompute > 20L) { // Folia - region threading + if (this.targetPos != null) { + this.path = null; + this.path = this.createPath(this.targetPos, this.reachRange); +- this.timeLastRecompute = this.level.getGameTime(); ++ this.timeLastRecompute = this.tick; // Folia - region threading + this.hasDelayedRecomputation = false; + } + } else { +@@ -221,7 +221,7 @@ public abstract class PathNavigation { + + public boolean moveTo(Entity entity, double speed) { + // Paper start - Perf: Optimise pathfinding +- if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) { ++ if (this.pathfindFailures > 10 && this.path == null && this.tick < this.lastFailure + 40) { // Folia - region threading + return false; + } + // Paper end - Perf: Optimise pathfinding +@@ -233,7 +233,7 @@ public abstract class PathNavigation { + return true; + } else { + this.pathfindFailures++; +- this.lastFailure = net.minecraft.server.MinecraftServer.currentTick; ++ this.lastFailure = this.tick; // Folia - region threading + return false; + } + // Paper end - Perf: Optimise pathfinding +diff --git a/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +index 6233e6b48aaa69ba9f577d0b480b1cdf2f55d16e..a4810c8a3b18082543d06787722d4ed5821a1943 100644 +--- a/net/minecraft/world/entity/ai/sensing/PlayerSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +@@ -22,7 +22,7 @@ public class PlayerSensor extends Sensor { + + @Override + protected void doTick(ServerLevel level, LivingEntity entity) { +- List list = level.players() ++ List list = level.getLocalPlayers() // Folia - region threading + .stream() + .filter(EntitySelector.NO_SPECTATORS) + .filter(serverPlayer -> entity.closerThan(serverPlayer, this.getFollowDistance(entity))) +diff --git a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +index 4b3ba795bc18417f983600f1edbc1895ccb7deab..d06c2c72e6166bc8b7822966092b17440125b814 100644 +--- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +@@ -36,7 +36,7 @@ public class TemptingSensor extends Sensor { + protected void doTick(ServerLevel level, PathfinderMob entity) { + Brain brain = entity.getBrain(); + TargetingConditions targetingConditions = TEMPT_TARGETING.copy().range((float)entity.getAttributeValue(Attributes.TEMPT_RANGE)); +- List list = level.players() ++ List list = level.getLocalPlayers() // Folia - region threading + .stream() + .filter(EntitySelector.NO_SPECTATORS) + .filter(serverPlayer -> targetingConditions.test(level, entity, serverPlayer)) +diff --git a/net/minecraft/world/entity/ai/village/VillageSiege.java b/net/minecraft/world/entity/ai/village/VillageSiege.java +index a1cea4a4f76a7bb771b8ab643bd9d473e16418bf..fa012c5b23f6fdd714d15282cc485492ae18672a 100644 +--- a/net/minecraft/world/entity/ai/village/VillageSiege.java ++++ b/net/minecraft/world/entity/ai/village/VillageSiege.java +@@ -18,68 +18,72 @@ import org.slf4j.Logger; + + public class VillageSiege implements CustomSpawner { + private static final Logger LOGGER = LogUtils.getLogger(); +- private boolean hasSetupSiege; +- private VillageSiege.State siegeState = VillageSiege.State.SIEGE_DONE; +- private int zombiesToSpawn; +- private int nextSpawnTime; +- private int spawnX; +- private int spawnY; +- private int spawnZ; ++ // Folia - region threading + + @Override + public int tick(ServerLevel level, boolean spawnHostiles, boolean spawnPassives) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading ++ // Folia start - region threading ++ // check if the spawn pos is no longer owned by this region ++ if (worldData.villageSiegeState.siegeState != State.SIEGE_DONE ++ && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, worldData.villageSiegeState.spawnX >> 4, worldData.villageSiegeState.spawnZ >> 4, 8)) { ++ // can't spawn here, just re-set ++ worldData.villageSiegeState = new io.papermc.paper.threadedregions.RegionizedWorldData.VillageSiegeState(); ++ } ++ // Folia end - region threading + if (!level.isDay() && spawnHostiles) { + float timeOfDay = level.getTimeOfDay(0.0F); + if (timeOfDay == 0.5) { +- this.siegeState = level.random.nextInt(10) == 0 ? VillageSiege.State.SIEGE_TONIGHT : VillageSiege.State.SIEGE_DONE; ++ worldData.villageSiegeState.siegeState = level.random.nextInt(10) == 0 ? VillageSiege.State.SIEGE_TONIGHT : VillageSiege.State.SIEGE_DONE; // Folia - region threading + } + +- if (this.siegeState == VillageSiege.State.SIEGE_DONE) { ++ if (worldData.villageSiegeState.siegeState == VillageSiege.State.SIEGE_DONE) { // Folia - region threading + return 0; + } else { +- if (!this.hasSetupSiege) { ++ if (!worldData.villageSiegeState.hasSetupSiege) { // Folia - region threading + if (!this.tryToSetupSiege(level)) { + return 0; + } + +- this.hasSetupSiege = true; ++ worldData.villageSiegeState.hasSetupSiege = true; // Folia - region threading + } + +- if (this.nextSpawnTime > 0) { +- this.nextSpawnTime--; ++ if (worldData.villageSiegeState.nextSpawnTime > 0) { // Folia - region threading ++ worldData.villageSiegeState.nextSpawnTime--; // Folia - region threading + return 0; + } else { +- this.nextSpawnTime = 2; +- if (this.zombiesToSpawn > 0) { ++ worldData.villageSiegeState.nextSpawnTime = 2; // Folia - region threading ++ if (worldData.villageSiegeState.zombiesToSpawn > 0) { // Folia - region threading + this.trySpawn(level); +- this.zombiesToSpawn--; ++ worldData.villageSiegeState.zombiesToSpawn--; // Folia - region threading + } else { +- this.siegeState = VillageSiege.State.SIEGE_DONE; ++ worldData.villageSiegeState.siegeState = VillageSiege.State.SIEGE_DONE; // Folia - region threading + } + + return 1; + } + } + } else { +- this.siegeState = VillageSiege.State.SIEGE_DONE; +- this.hasSetupSiege = false; ++ worldData.villageSiegeState.siegeState = VillageSiege.State.SIEGE_DONE; // Folia - region threading ++ worldData.villageSiegeState.hasSetupSiege = false; // Folia - region threading + return 0; + } + } + + private boolean tryToSetupSiege(ServerLevel level) { +- for (Player player : level.players()) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading ++ for (Player player : level.getLocalPlayers()) { // Folia - region threading + if (!player.isSpectator()) { + BlockPos blockPos = player.blockPosition(); + if (level.isVillage(blockPos) && !level.getBiome(blockPos).is(BiomeTags.WITHOUT_ZOMBIE_SIEGES)) { + for (int i = 0; i < 10; i++) { + float f = level.random.nextFloat() * (float) (Math.PI * 2); +- this.spawnX = blockPos.getX() + Mth.floor(Mth.cos(f) * 32.0F); +- this.spawnY = blockPos.getY(); +- this.spawnZ = blockPos.getZ() + Mth.floor(Mth.sin(f) * 32.0F); +- if (this.findRandomSpawnPos(level, new BlockPos(this.spawnX, this.spawnY, this.spawnZ)) != null) { +- this.nextSpawnTime = 0; +- this.zombiesToSpawn = 20; ++ worldData.villageSiegeState.spawnX = blockPos.getX() + Mth.floor(Mth.cos(f) * 32.0F); // Folia - region threading ++ worldData.villageSiegeState.spawnY = blockPos.getY(); // Folia - region threading ++ worldData.villageSiegeState.spawnZ = blockPos.getZ() + Mth.floor(Mth.sin(f) * 32.0F); // Folia - region threading ++ if (this.findRandomSpawnPos(level, new BlockPos(worldData.villageSiegeState.spawnX, worldData.villageSiegeState.spawnY, worldData.villageSiegeState.spawnZ)) != null) { // Folia - region threading ++ worldData.villageSiegeState.nextSpawnTime = 0; // Folia - region threading ++ worldData.villageSiegeState.zombiesToSpawn = 20; // Folia - region threading + break; + } + } +@@ -93,11 +97,13 @@ public class VillageSiege implements CustomSpawner { + } + + private void trySpawn(ServerLevel level) { +- Vec3 vec3 = this.findRandomSpawnPos(level, new BlockPos(this.spawnX, this.spawnY, this.spawnZ)); ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading ++ Vec3 vec3 = this.findRandomSpawnPos(level, new BlockPos(worldData.villageSiegeState.spawnX, worldData.villageSiegeState.spawnY, worldData.villageSiegeState.spawnZ)); // Folia - region threading + if (vec3 != null) { + Zombie zombie; + try { + zombie = new Zombie(level); ++ zombie.moveTo(vec3.x, vec3.y, vec3.z, level.random.nextFloat() * 360.0F, 0.0F); // Folia - region threading - move up + zombie.finalizeSpawn(level, level.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.EVENT, null); + } catch (Exception var5) { + LOGGER.warn("Failed to create zombie for village siege at {}", vec3, var5); +@@ -105,7 +111,7 @@ public class VillageSiege implements CustomSpawner { + return; + } + +- zombie.moveTo(vec3.x, vec3.y, vec3.z, level.random.nextFloat() * 360.0F, 0.0F); ++ //zombie.moveTo(vec3.x, vec3.y, vec3.z, level.random.nextFloat() * 360.0F, 0.0F); // Folia - region threading - move up + level.addFreshEntityWithPassengers(zombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); // CraftBukkit + } + } +@@ -125,7 +131,7 @@ public class VillageSiege implements CustomSpawner { + return null; + } + +- static enum State { ++ public static enum State { // Folia - region threading + SIEGE_CAN_ACTIVATE, + SIEGE_TONIGHT, + SIEGE_DONE; +diff --git a/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index 618fc0eb4fe70e46e55f3aa28e8eac1d2d01b6d9..c10810bf00d75f459c3c6a9415c1e09f0519d50e 100644 +--- a/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -58,11 +58,13 @@ public class PoiManager extends SectionStorage im + } + + private void updateDistanceTracking(long section) { ++ synchronized (this.villageDistanceTracker) { // Folia - region threading + if (this.isVillageCenter(section)) { + this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); + } else { + this.villageDistanceTracker.removeSource(section); + } ++ } // Folia - region threading + } + + @Override +@@ -347,10 +349,12 @@ public class PoiManager extends SectionStorage im + } + + public int sectionsToVillage(SectionPos sectionPos) { ++ synchronized (this.villageDistanceTracker) { // Folia - region threading + // Paper start - rewrite chunk system + this.villageDistanceTracker.propagateUpdates(); + return convertBetweenLevels(this.villageDistanceTracker.getLevel(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(sectionPos))); + // Paper end - rewrite chunk system ++ } // Folia - region threading + } + + boolean isVillageCenter(long chunkPos) { +@@ -364,7 +368,9 @@ public class PoiManager extends SectionStorage im + + @Override + public void tick(BooleanSupplier aheadOfTime) { ++ synchronized (this.villageDistanceTracker) { // Folia - region threading + this.villageDistanceTracker.propagateUpdates(); // Paper - rewrite chunk system ++ } // Folia - region threading + } + + @Override +diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java +index 94244b148533ef026bf5c56abbc2bb5cfa83c938..15360f560d9b6a762ebd4284b7d0ca0a3e13794e 100644 +--- a/net/minecraft/world/entity/animal/Bee.java ++++ b/net/minecraft/world/entity/animal/Bee.java +@@ -815,6 +815,11 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + @Override + public boolean canBeeUse() { ++ // Folia start - region threading ++ if (Bee.this.hivePos != null && Bee.this.isTooFarAway(Bee.this.hivePos)) { ++ Bee.this.hivePos = null; ++ } ++ // Folia end - region threading + return Bee.this.hivePos != null + && !Bee.this.isTooFarAway(Bee.this.hivePos) + && !Bee.this.hasRestriction() +@@ -925,6 +930,11 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + @Override + public boolean canBeeUse() { ++ // Folia start - region threading ++ if (Bee.this.savedFlowerPos != null && Bee.this.isTooFarAway(Bee.this.savedFlowerPos)) { ++ Bee.this.savedFlowerPos = null; ++ } ++ // Folia end - region threading + return Bee.this.savedFlowerPos != null + && !Bee.this.hasRestriction() + && this.wantsToGoToKnownFlower() +diff --git a/net/minecraft/world/entity/animal/Cat.java b/net/minecraft/world/entity/animal/Cat.java +index 1a7a5c81a260cc740994d1a63c4775c41c238dea..740ab58c733d9e3f05157fef6e6725fd72f90653 100644 +--- a/net/minecraft/world/entity/animal/Cat.java ++++ b/net/minecraft/world/entity/animal/Cat.java +@@ -342,7 +342,7 @@ public class Cat extends TamableAnimal implements VariantHolder tagKey = flag ? CatVariantTags.FULL_MOON_SPAWNS : CatVariantTags.DEFAULT_SPAWNS; + BuiltInRegistries.CAT_VARIANT.getRandomElementOf(tagKey, level.getRandom()).ifPresent(this::setVariant); + ServerLevel level1 = level.getLevel(); +- if (level1.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK, level).isValid()) { // Paper - Fix swamp hut cat generation deadlock ++ if (level.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK).isValid()) { // Paper - Fix swamp hut cat generation deadlock // Folia - region threading - properly fix this + this.setVariant(BuiltInRegistries.CAT_VARIANT.getOrThrow(CatVariant.ALL_BLACK)); + this.setPersistenceRequired(); + } +diff --git a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +index ff1c84d37db48e1bd0283a900e199647c0e8eba1..fc64c36a01eb8efdcfa487059078787900e34d86 100644 +--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java ++++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +@@ -53,7 +53,7 @@ public class EndCrystal extends Entity { + public void tick() { + this.time++; + this.applyEffectsFromBlocks(); +- this.handlePortal(); ++ //this.handlePortal(); // Folia - region threading + if (this.level() instanceof ServerLevel) { + BlockPos blockPos = this.blockPosition(); + if (((ServerLevel)this.level()).getDragonFight() != null && this.level().getBlockState(blockPos).isAir()) { +diff --git a/net/minecraft/world/entity/decoration/ItemFrame.java b/net/minecraft/world/entity/decoration/ItemFrame.java +index 65e1d7c5ac94b1cfb921fa009be59d3e5872f0b5..5aefec7ecc2085659bebca25992dd3a76fff2b5e 100644 +--- a/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -242,7 +242,9 @@ public class ItemFrame extends HangingEntity { + if (framedMapId != null) { + MapItemSavedData savedData = MapItem.getSavedData(framedMapId, this.level()); + if (savedData != null) { ++ synchronized (savedData) { // Folia - make map data thread-safe + savedData.removedFromFrame(this.pos, this.getId()); ++ } // Folia - make map data thread-safe + } + } + +diff --git a/net/minecraft/world/entity/item/FallingBlockEntity.java b/net/minecraft/world/entity/item/FallingBlockEntity.java +index 5746587666c7cb788764aab2f6ccf0f3ac5c282f..1fa5e6a12b943e889bde566038a632a6adcf319e 100644 +--- a/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -162,7 +162,7 @@ public class FallingBlockEntity extends Entity { + return; + } + // Paper end - Configurable falling blocks height nerf +- this.handlePortal(); ++ //this.handlePortal(); // Folia - region threading + if (this.level() instanceof ServerLevel serverLevel && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) { + BlockPos blockPos = this.blockPosition(); + boolean flag = this.blockState.getBlock() instanceof ConcretePowderBlock; +diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java +index 52a7ed0d991758bad0dcedcb7f97fb15ac6c6d04..7587130e021d494ae5013f7992b8f3c96590cbd7 100644 +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -521,13 +521,21 @@ public class ItemEntity extends Entity implements TraceableEntity { + return false; + } + ++ // Folia start - region threading ++ @Override ++ public void postChangeDimension() { ++ super.postChangeDimension(); ++ if (!this.level().isClientSide) { ++ this.mergeWithNeighbours(); ++ } ++ } ++ // Folia end - region threading ++ + @Nullable + @Override + public Entity teleport(TeleportTransition teleportTransition) { + Entity entity = super.teleport(teleportTransition); +- if (!this.level().isClientSide && entity instanceof ItemEntity itemEntity) { +- itemEntity.mergeWithNeighbours(); +- } ++ if (entity != null) entity.postChangeDimension(); // Folia - region threading - move to post change + + return entity; + } +diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java +index 40da052e7fea1306a007b3cb5c9daa33e0ef523e..88570bb4aa02896545805d7721c45cf9599befea 100644 +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -98,8 +98,8 @@ public class PrimedTnt extends Entity implements TraceableEntity { + + @Override + public void tick() { +- if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot +- this.handlePortal(); ++ if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().getCurrentWorldData().currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot // Folia - region threading ++ //this.handlePortal(); // Folia - region threading + this.applyGravity(); + this.move(MoverType.SELF, this.getDeltaMovement()); + this.applyEffectsFromBlocks(); +@@ -137,7 +137,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { + */ + // Send position and velocity updates to nearby players on every tick while the TNT is in water. + // This does pretty well at keeping their clients in sync with the server. +- net.minecraft.server.level.ChunkMap.TrackedEntity ete = ((net.minecraft.server.level.ServerLevel) this.level()).getChunkSource().chunkMap.entityMap.get(this.getId()); ++ net.minecraft.server.level.ChunkMap.TrackedEntity ete = this.moonrise$getTrackedEntity(); // Folia - region threading + if (ete != null) { + net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket velocityPacket = new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(this); + net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket positionPacket = net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket.teleport(this.getId(), net.minecraft.world.entity.PositionMoveRotation.of(this), java.util.Set.of(), this.onGround); +diff --git a/net/minecraft/world/entity/monster/Vex.java b/net/minecraft/world/entity/monster/Vex.java +index af3fef70998cff4e4832adfa2071832324ebd91c..8751f80d48d11c33ddb6c553894c31e8b7630623 100644 +--- a/net/minecraft/world/entity/monster/Vex.java ++++ b/net/minecraft/world/entity/monster/Vex.java +@@ -349,7 +349,7 @@ public class Vex extends Monster implements TraceableEntity { + @Override + public void tick() { + BlockPos boundOrigin = Vex.this.getBoundOrigin(); +- if (boundOrigin == null) { ++ if (boundOrigin == null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)Vex.this.level(), boundOrigin)) { // Folia - region threading + boundOrigin = Vex.this.blockPosition(); + } + +diff --git a/net/minecraft/world/entity/monster/ZombieVillager.java b/net/minecraft/world/entity/monster/ZombieVillager.java +index 9061e0b6544d6a31a4dc5b51037f608031a00553..76fa0a25fe084f17045f72a1750c6e8b1eb7cb14 100644 +--- a/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -69,7 +69,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + @Nullable + private MerchantOffers tradeOffers; + private int villagerXp; +- private int lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit - add field ++ //private int lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit - add field // Folia - region threading - restore original timers + + public ZombieVillager(EntityType entityType, Level level) { + super(entityType, level); +@@ -149,7 +149,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + } + + super.tick(); +- this.lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit ++ //this.lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit // Folia - region threading - restore original timers + } + + @Override +diff --git a/net/minecraft/world/entity/npc/AbstractVillager.java b/net/minecraft/world/entity/npc/AbstractVillager.java +index a71d16d968bb90fd7aca6f01a3dd56df4f9a7ce6..27ce04ecee778b73711ee55c7c75c541e1f86c38 100644 +--- a/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -218,10 +218,18 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa + this.readInventoryFromTag(compound, this.registryAccess()); + } + ++ // Folia start - region threading ++ @Override ++ public void preChangeDimension() { ++ super.preChangeDimension(); ++ this.stopTrading(); ++ } ++ // Folia end - region threading ++ + @Nullable + @Override + public Entity teleport(TeleportTransition teleportTransition) { +- this.stopTrading(); ++ this.preChangeDimension(); // Folia - region threading - move into preChangeDimension + return super.teleport(teleportTransition); + } + +diff --git a/net/minecraft/world/entity/npc/CatSpawner.java b/net/minecraft/world/entity/npc/CatSpawner.java +index e6d368bc601357cfca694ce328c8e6e47491f3b5..010bee26dfdf5cad186fa57c030540693ff71f23 100644 +--- a/net/minecraft/world/entity/npc/CatSpawner.java ++++ b/net/minecraft/world/entity/npc/CatSpawner.java +@@ -18,17 +18,18 @@ import net.minecraft.world.phys.AABB; + + public class CatSpawner implements CustomSpawner { + private static final int TICK_DELAY = 1200; +- private int nextTick; ++ //private int nextTick; // Folia - region threading + + @Override + public int tick(ServerLevel level, boolean spawnHostiles, boolean spawnPassives) { + if (spawnPassives && level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { +- this.nextTick--; +- if (this.nextTick > 0) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading ++ worldData.catSpawnerNextTick--; // Folia - region threading ++ if (worldData.catSpawnerNextTick > 0) { // Folia - region threading + return 0; + } else { +- this.nextTick = 1200; +- Player randomPlayer = level.getRandomPlayer(); ++ worldData.catSpawnerNextTick = 1200; // Folia - region threading ++ Player randomPlayer = level.getRandomLocalPlayer(); // Folia - region threading + if (randomPlayer == null) { + return 0; + } else { +diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java +index 2b83262e4a13eae86df82913ce4f3121e3631a43..7ea74aeb905b95e5919d74df5fbc5e8f7a9985e3 100644 +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -246,7 +246,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + villagerBrain.setCoreActivities(ImmutableSet.of(Activity.CORE)); + villagerBrain.setDefaultActivity(Activity.IDLE); + villagerBrain.setActiveActivityIfPossible(Activity.IDLE); +- villagerBrain.updateActivityFromSchedule(this.level().getDayTime(), this.level().getGameTime()); ++ villagerBrain.updateActivityFromSchedule(this.level().getLevelData().getDayTime(), this.level().getLevelData().getGameTime()); // Folia - region threading - not in the world yet + } + + @Override +@@ -693,6 +693,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.brain.getMemory(moduleType).ifPresent(globalPos -> { + ServerLevel level = server.getLevel(globalPos.dimension()); + if (level != null) { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( // Folia - region threading ++ level, globalPos.pos().getX() >> 4, globalPos.pos().getZ() >> 4, () -> { // Folia - region threading + PoiManager poiManager = level.getPoiManager(); + Optional> type = poiManager.getType(globalPos.pos()); + BiPredicate> biPredicate = POI_MEMORIES.get(moduleType); +@@ -700,6 +702,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + poiManager.release(globalPos.pos()); + DebugPackets.sendPoiTicketCountPacket(level, globalPos.pos()); + } ++ }); // Folia - region threading + } + }); + } +diff --git a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index ef2afb17a22a703470e13d12c989a685e72f0ab8..984ac8efa2ed45be614e04eab8247481e3a08525 100644 +--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -30,16 +30,14 @@ public class WanderingTraderSpawner implements CustomSpawner { + private static final int SPAWN_CHANCE_INCREASE = 25; + private static final int SPAWN_ONE_IN_X_CHANCE = 10; + private static final int NUMBER_OF_SPAWN_ATTEMPTS = 10; +- private final RandomSource random = RandomSource.create(); ++ private final RandomSource random = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Folia - region threading + private final ServerLevelData serverLevelData; +- private int tickDelay; +- private int spawnDelay; +- private int spawnChance; ++ // Folia - region threading + + public WanderingTraderSpawner(ServerLevelData serverLevelData) { + this.serverLevelData = serverLevelData; + // Paper start - Add Wandering Trader spawn rate config options +- this.tickDelay = Integer.MIN_VALUE; ++ //this.tickDelay = Integer.MIN_VALUE; // Folia - region threading - moved to regionisedworlddata + // this.spawnDelay = serverLevelData.getWanderingTraderSpawnDelay(); + // this.spawnChance = serverLevelData.getWanderingTraderSpawnChance(); + // if (this.spawnDelay == 0 && this.spawnChance == 0) { +@@ -53,35 +51,36 @@ public class WanderingTraderSpawner implements CustomSpawner { + + @Override + public int tick(ServerLevel level, boolean spawnHostiles, boolean spawnPassives) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading + // Paper start - Add Wandering Trader spawn rate config options +- if (this.tickDelay == Integer.MIN_VALUE) { +- this.tickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; +- this.spawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; +- this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ if (worldData.wanderingTraderTickDelay == Integer.MIN_VALUE) { // Folia - region threading ++ worldData.wanderingTraderTickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; // Folia - region threading ++ worldData.wanderingTraderSpawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; // Folia - region threading ++ worldData.wanderingTraderSpawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; // Folia - region threading + } + if (!level.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { + return 0; +- } else if (--this.tickDelay - 1 > 0) { +- this.tickDelay = this.tickDelay - 1; ++ } else if (--worldData.wanderingTraderTickDelay - 1 > 0) { // Folia - region threading ++ worldData.wanderingTraderTickDelay = worldData.wanderingTraderTickDelay - 1; // Folia - region threading + return 0; + } else { +- this.tickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; +- this.spawnDelay = this.spawnDelay - level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ worldData.wanderingTraderTickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; // Folia - region threading ++ worldData.wanderingTraderSpawnDelay = worldData.wanderingTraderSpawnDelay - level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; // Folia - region threading + //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways +- if (this.spawnDelay > 0) { ++ if (worldData.wanderingTraderSpawnDelay > 0) { // Folia - region threading + return 0; + } else { +- this.spawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; ++ worldData.wanderingTraderSpawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; // Folia - region threading + if (!level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + return 0; + } else { +- int i = this.spawnChance; +- this.spawnChance = Mth.clamp(this.spawnChance + level.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); ++ int i = worldData.wanderingTraderSpawnChance; // Folia - region threading ++ worldData.wanderingTraderSpawnChance = Mth.clamp(worldData.wanderingTraderSpawnChance + level.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); // Folia - region threading + //this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.random.nextInt(100) > i) { + return 0; + } else if (this.spawn(level)) { +- this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ worldData.wanderingTraderSpawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; // Folia - region threading + // Paper end - Add Wandering Trader spawn rate config options + return 1; + } else { +@@ -93,7 +92,7 @@ public class WanderingTraderSpawner implements CustomSpawner { + } + + private boolean spawn(ServerLevel serverLevel) { +- Player randomPlayer = serverLevel.getRandomPlayer(); ++ Player randomPlayer = serverLevel.getRandomLocalPlayer(); // Folia - region threading + if (randomPlayer == null) { + return true; + } else if (this.random.nextInt(10) != 0) { +@@ -116,7 +115,7 @@ public class WanderingTraderSpawner implements CustomSpawner { + this.tryToSpawnLlamaFor(serverLevel, wanderingTrader, 4); + } + +- this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID()); ++ //this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID()); // Folia - region threading - doesn't appear to be used anywhere, so avoid the race condition here... + // wanderingTrader.setDespawnDelay(48000); // Paper - moved above, modifiable by plugins on CreatureSpawnEvent + wanderingTrader.setWanderTarget(blockPos1); + wanderingTrader.restrictTo(blockPos1, 16); +diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java +index 092acdc1e41582b7b873af926ce3cf3e5ff23b67..d15304ec04bf22674ccd0b106f5d9ba7e609f5d0 100644 +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -1506,6 +1506,14 @@ public abstract class Player extends LivingEntity { + } + } + ++ // Folia start - region threading ++ @Override ++ protected void preRemove(RemovalReason reason) { ++ super.preRemove(reason); ++ this.fishing = null; ++ } ++ // Folia end - region threading ++ + public boolean isLocalPlayer() { + return false; + } +diff --git a/net/minecraft/world/entity/projectile/AbstractArrow.java b/net/minecraft/world/entity/projectile/AbstractArrow.java +index 23a795eb4c7ad968448dd1405272056bac29c8f8..1a771e8510b2511945d253a1a5ad23054c464b0c 100644 +--- a/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -176,6 +176,11 @@ public abstract class AbstractArrow extends Projectile { + + @Override + public void tick() { ++ // Folia start - region threading - make sure entities do not move into regions they do not own ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { ++ return; ++ } ++ // Folia end - region threading - make sure entities do not move into regions they do not own + boolean flag = !this.isNoPhysics(); + Vec3 deltaMovement = this.getDeltaMovement(); + BlockPos blockPos = this.blockPosition(); +diff --git a/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java b/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +index 9a99b813de8b606fab26c87086a21372e5172ba3..4eeb1017576d23d206a7a47b9e9e74b19465b2ae 100644 +--- a/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java ++++ b/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +@@ -80,6 +80,11 @@ public abstract class AbstractHurtingProjectile extends Projectile { + this.setPos(location); + this.applyEffectsFromBlocks(); + super.tick(); ++ // Folia start - region threading - make sure entities do not move into regions they do not own ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { ++ return; ++ } ++ // Folia end - region threading - make sure entities do not move into regions they do not own + if (this.shouldBurn()) { + this.igniteForSeconds(1.0F); + } +diff --git a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +index 774ca9e0b56fd175ae246051de762d0c4256ca58..0cfd2c937f93f1acb4afc01251f882710baf2591 100644 +--- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java ++++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +@@ -130,6 +130,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { + } + }); + } ++ // Folia start - region threading ++ if (this.attachedToEntity != null && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.attachedToEntity)) { ++ this.attachedToEntity = null; ++ } ++ // Folia end - region threading + + if (this.attachedToEntity != null) { + Vec3 handHoldingItemAngle; +diff --git a/net/minecraft/world/entity/projectile/FishingHook.java b/net/minecraft/world/entity/projectile/FishingHook.java +index 1e012c7ef699a64ff3f1b00f897bb893ab25ecbd..f9d7514764850fd02ed5853ba2fdf8ada40ce756 100644 +--- a/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/net/minecraft/world/entity/projectile/FishingHook.java +@@ -94,7 +94,7 @@ public class FishingHook extends Projectile { + + public FishingHook(Player player, Level level, int luck, int lureSpeed) { + this(EntityType.FISHING_BOBBER, level, luck, lureSpeed); +- this.setOwner(player); ++ //this.setOwner(player); // Folia - region threading - move this down after position so that thread-checks do not fail + float xRot = player.getXRot(); + float yRot = player.getYRot(); + float cos = Mth.cos(-yRot * (float) (Math.PI / 180.0) - (float) Math.PI); +@@ -105,6 +105,7 @@ public class FishingHook extends Projectile { + double eyeY = player.getEyeY(); + double d1 = player.getZ() - cos * 0.3; + this.moveTo(d, eyeY, d1, yRot, xRot); ++ this.setOwner(player); // Folia - region threading - move this down after position so that thread-checks do not fail + Vec3 vec3 = new Vec3(-sin, Mth.clamp(-(sin1 / f), -5.0F, 5.0F), -cos); + double len = vec3.length(); + vec3 = vec3.multiply( +@@ -260,6 +261,11 @@ public class FishingHook extends Projectile { + } + + private boolean shouldStopFishing(Player player) { ++ // Folia start - region threading ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) { ++ return true; ++ } ++ // Folia end - region threading + ItemStack mainHandItem = player.getMainHandItem(); + ItemStack offhandItem = player.getOffhandItem(); + boolean isFishingRod = mainHandItem.is(Items.FISHING_ROD); +@@ -623,10 +629,18 @@ public class FishingHook extends Projectile { + @Override + public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) { + // CraftBukkit end +- this.updateOwnerInfo(null); ++ //this.updateOwnerInfo(null); // Folia - region threading - move into preRemove + super.remove(reason, cause); // CraftBukkit - add Bukkit remove cause + } + ++ // Folia start - region threading ++ @Override ++ protected void preRemove(RemovalReason reason) { ++ super.preRemove(reason); ++ this.updateOwnerInfo(null); ++ } ++ // Folia end - region threading ++ + @Override + public void onClientRemoval() { + this.updateOwnerInfo(null); +diff --git a/net/minecraft/world/entity/projectile/LlamaSpit.java b/net/minecraft/world/entity/projectile/LlamaSpit.java +index 4880db97135d54fa72f64c108b2bd4ded096438b..dc6ec52a513e2754a81733de5f389d6ada5215cc 100644 +--- a/net/minecraft/world/entity/projectile/LlamaSpit.java ++++ b/net/minecraft/world/entity/projectile/LlamaSpit.java +@@ -41,6 +41,11 @@ public class LlamaSpit extends Projectile { + @Override + public void tick() { + super.tick(); ++ // Folia start - region threading - make sure entities do not move into regions they do not own ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { ++ return; ++ } ++ // Folia end - region threading - make sure entities do not move into regions they do not own + Vec3 deltaMovement = this.getDeltaMovement(); + HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); + this.preHitTargetOrDeflectSelf(hitResultOnMoveVector); // CraftBukkit - projectile hit event +diff --git a/net/minecraft/world/entity/projectile/Projectile.java b/net/minecraft/world/entity/projectile/Projectile.java +index ad0bb896d6ea669ce88bfe6490319e8ba7a29001..abfe6765faec49d4b8897608582d738c7b09522d 100644 +--- a/net/minecraft/world/entity/projectile/Projectile.java ++++ b/net/minecraft/world/entity/projectile/Projectile.java +@@ -38,7 +38,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + @Nullable + public UUID ownerUUID; + @Nullable +- public Entity cachedOwner; ++ public org.bukkit.craftbukkit.entity.CraftEntity cachedOwner; // Folia - region threading - replace with CraftEntity + public boolean leftOwner; + public boolean hasBeenShot; + @Nullable +@@ -52,7 +52,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + public void setOwner(@Nullable Entity owner) { + if (owner != null) { + this.ownerUUID = owner.getUUID(); +- this.cachedOwner = owner; ++ this.cachedOwner = owner.getBukkitEntity(); // Folia - region threading + } + // Paper start - Refresh ProjectileSource for projectiles + else { +@@ -69,22 +69,38 @@ public abstract class Projectile extends Entity implements TraceableEntity { + if (fillCache) { + this.getOwner(); + } +- if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof org.bukkit.projectiles.ProjectileSource projSource) { ++ if (this.cachedOwner != null && !this.cachedOwner.getHandleRaw().isRemoved() && this.projectileSource == null && this.cachedOwner instanceof org.bukkit.projectiles.ProjectileSource projSource) { // Folia - region threading + this.projectileSource = projSource; + } + } + // Paper end - Refresh ProjectileSource for projectiles + ++ // Folia start - region threading ++ // In general, this is an entire mess. At the time of writing, there are fifty usages of getOwner. ++ // Usage of this function is to avoid concurrency issues, even if it sacrifices behavior. + @Nullable + @Override + public Entity getOwner() { +- if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) { ++ Entity ret = this.getOwnerRaw(); ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(ret) && (ret == null || !ret.isRemoved()) ? ret : null; ++ } ++ // Folia end - region threading ++ ++ @Nullable ++ public Entity getOwnerRaw() { // Folia - region threading ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot update owner state asynchronously"); // Folia - region threading ++ if (this.cachedOwner != null && !this.cachedOwner.isPurged()) { // Folia - region threading + this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles +- return this.cachedOwner; ++ return this.cachedOwner.getHandleRaw(); // Folia - region threading + } else if (this.ownerUUID != null) { +- this.cachedOwner = this.findOwner(this.ownerUUID); ++ // Folia start - region threading ++ Entity ret = this.findOwner(this.ownerUUID); ++ if (ret != null) { ++ this.cachedOwner = ret.getBukkitEntity(); ++ } ++ // Folia end - region threading + this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles +- return this.cachedOwner; ++ return ret; // Folia - region threading + } else { + return null; + } +@@ -130,7 +146,12 @@ public abstract class Projectile extends Entity implements TraceableEntity { + protected void setOwnerThroughUUID(UUID uuid) { + if (this.ownerUUID != uuid) { + this.ownerUUID = uuid; +- this.cachedOwner = this.findOwner(uuid); ++ // Folia start - region threading ++ Entity cachedOwner = this.findOwner(this.ownerUUID); ++ if (cachedOwner != null) { ++ this.cachedOwner = cachedOwner.getBukkitEntity(); ++ } ++ // Folia end - region threading + } + } + +@@ -454,7 +475,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { + @Override + public boolean mayInteract(ServerLevel level, BlockPos pos) { + Entity owner = this.getOwner(); +- return owner instanceof Player ? owner.mayInteract(level, pos) : owner == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); ++ return owner instanceof Player && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(owner) ? owner.mayInteract(level, pos) : owner == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Folia - region threading + } + + public boolean mayBreak(ServerLevel level) { +diff --git a/net/minecraft/world/entity/projectile/SmallFireball.java b/net/minecraft/world/entity/projectile/SmallFireball.java +index 8c84cea43fc0e42a576004663670977eac99f1a6..ba70ce3df630532b646eab0a5fabca15d67c379b 100644 +--- a/net/minecraft/world/entity/projectile/SmallFireball.java ++++ b/net/minecraft/world/entity/projectile/SmallFireball.java +@@ -24,7 +24,7 @@ public class SmallFireball extends Fireball { + public SmallFireball(Level level, LivingEntity owner, Vec3 movement) { + super(EntityType.SMALL_FIREBALL, owner, movement, level); + // CraftBukkit start +- if (this.getOwner() != null && this.getOwner() instanceof Mob) { ++ if (owner != null && this.getOwner() != null && this.getOwner() instanceof Mob) { // Folia - region threading + this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + } + // CraftBukkit end +diff --git a/net/minecraft/world/entity/projectile/ThrowableProjectile.java b/net/minecraft/world/entity/projectile/ThrowableProjectile.java +index f9fa2866cb28622785b4fcd54c0e2989569a401a..74590ac276965543c2d78fe85090097c8d3a7aed 100644 +--- a/net/minecraft/world/entity/projectile/ThrowableProjectile.java ++++ b/net/minecraft/world/entity/projectile/ThrowableProjectile.java +@@ -43,6 +43,11 @@ public abstract class ThrowableProjectile extends Projectile { + + @Override + public void tick() { ++ // Folia start - region threading - make sure entities do not move into regions they do not own ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { ++ return; ++ } ++ // Folia end - region threading - make sure entities do not move into regions they do not own + this.handleFirstTickBubbleColumn(); + this.applyGravity(); + this.applyInertia(); +diff --git a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +index 1345097a2a417f95c44143fd7e0d4cec38990121..0a2b4d6da836d7907759b6cdc94afd031450018d 100644 +--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java ++++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +@@ -58,15 +58,11 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + } + + private void deregisterFromCurrentOwner() { +- if (this.getOwner() instanceof ServerPlayer serverPlayer) { +- serverPlayer.deregisterEnderPearl(this); +- } ++ // Folia - region threading - we remove the registration logic, we do not need to fetch the owner + } + + private void registerToCurrentOwner() { +- if (this.getOwner() instanceof ServerPlayer serverPlayer) { +- serverPlayer.registerEnderPearl(this); +- } ++ // Folia - region threading - we remove the registration logic, we do not need to fetch the owner + } + + @Nullable +@@ -99,6 +95,81 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + result.getEntity().hurt(this.damageSources().thrown(this, this.getOwner()), 0.0F); + } + ++ // Folia start - region threading ++ private static void attemptTeleport(Entity source, ServerLevel checkWorld, net.minecraft.world.phys.Vec3 to) { ++ final boolean onPortalCooldown = source.isOnPortalCooldown(); ++ // ignore retired callback, in those cases we do not want to teleport ++ source.getBukkitEntity().taskScheduler.schedule( ++ (Entity entity) -> { ++ if (!isAllowedToTeleportOwner(entity, checkWorld)) { ++ return; ++ } ++ // source is now an invalid reference, do not use it, use the entity parameter ++ net.minecraft.world.phys.Vec3 endermitePos = entity.position(); ++ ++ // dismount from any vehicles, so we can teleport and to prevent desync ++ if (entity.isPassenger()) { ++ entity.unRide(); ++ } ++ ++ if (onPortalCooldown) { ++ entity.setPortalCooldown(); ++ } ++ ++ entity.teleportAsync( ++ checkWorld, to, null, null, null, ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL, ++ // chunk could have been unloaded ++ Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS | Entity.TELEPORT_FLAG_LOAD_CHUNK, ++ (Entity teleported) -> { ++ // entity is now an invalid reference, do not use it, instead use teleported ++ if (teleported instanceof ServerPlayer player) { ++ // connection teleport is already done ++ ServerLevel world = player.serverLevel(); ++ ++ // endermite spawn chance ++ if (world.random.nextFloat() < 0.05F && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { ++ Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(world, EntitySpawnReason.TRIGGERED); ++ ++ if (entityendermite != null) { ++ float yRot = teleported.getYRot(); ++ float xRot = teleported.getXRot(); ++ Runnable spawn = () -> { ++ entityendermite.moveTo(endermitePos.x, endermitePos.y, endermitePos.z, yRot, xRot); ++ world.addFreshEntity(entityendermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL); ++ }; ++ ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(world, endermitePos, net.minecraft.world.phys.Vec3.ZERO, 1)) { ++ spawn.run(); ++ } else { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( ++ world, ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkCoordinate(endermitePos.x), ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkCoordinate(endermitePos.z), ++ spawn ++ ); ++ } ++ } ++ } ++ ++ // damage player ++ teleported.resetFallDistance(); ++ player.resetCurrentImpulseContext(); ++ player.hurtServer(player.serverLevel(), player.damageSources().enderPearl().eventEntityDamager(player), 5.0F); // CraftBukkit // Paper - fix DamageSource API ++ playSound(teleported.level(), to); ++ } else { ++ // reset fall damage so that if the entity was falling they do not instantly die ++ teleported.resetFallDistance(); ++ playSound(teleported.level(), to); ++ } ++ } ++ ); ++ }, ++ null, 1L ++ ); ++ } ++ // Folia end - region threading ++ + @Override + protected void onHit(HitResult result) { + super.onHit(result); +@@ -117,6 +188,20 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + } + + if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved()) { ++ // Folia start - region threading ++ if (true) { ++ // we can't fire events, because we do not actually know where the other entity is located ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this)) { ++ throw new IllegalStateException("Must be on tick thread for ticking entity: " + this); ++ } ++ Entity entity = this.getOwnerRaw(); ++ if (entity != null) { ++ attemptTeleport(entity, (ServerLevel)this.level(), this.position()); ++ } ++ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); ++ return; ++ } ++ // Folia end - region threading + Entity owner = this.getOwner(); + if (owner != null && isAllowedToTeleportOwner(owner, serverLevel)) { + if (owner.isPassenger()) { +@@ -212,7 +297,15 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { + } + } + +- private void playSound(Level level, Vec3 pos) { ++ // Folia start - region threading ++ @Override ++ public void preChangeDimension() { ++ super.preChangeDimension(); ++ // Don't change the owner here, since the tick logic will consider it anyways. ++ } ++ // Folia end - region threading ++ ++ private static void playSound(Level level, Vec3 pos) { // Folia - region threading - static + level.playSound(null, pos.x, pos.y, pos.z, SoundEvents.PLAYER_TELEPORT, SoundSource.PLAYERS); + } + +diff --git a/net/minecraft/world/entity/raid/Raid.java b/net/minecraft/world/entity/raid/Raid.java +index 41b0db439b425b052bd1469daa6620a435ca852b..2f45befbb50645f1bfb5961ad725f3670ff0d592 100644 +--- a/net/minecraft/world/entity/raid/Raid.java ++++ b/net/minecraft/world/entity/raid/Raid.java +@@ -110,6 +110,13 @@ public class Raid { + public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(PDC_TYPE_REGISTRY); + // Paper end + ++ // Folia start - make raids thread-safe ++ public boolean ownsRaid() { ++ BlockPos center = this.getCenter(); ++ return center != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, center.getX() >> 4, center.getZ() >> 4, 8); ++ } ++ // Folia end - make raids thread-safe ++ + public Raid(int id, ServerLevel level, BlockPos center) { + this.id = id; + this.level = level; +@@ -207,7 +214,7 @@ public class Raid { + private Predicate validPlayer() { + return player -> { + BlockPos blockPos = player.blockPosition(); +- return player.isAlive() && this.level.getRaidAt(blockPos) == this; ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player) && player.isAlive() && this.level.getRaidAt(blockPos) == this; // Folia - make raids thread-safe + }; + } + +@@ -384,14 +391,21 @@ public class Raid { + if (entity instanceof LivingEntity) { + LivingEntity livingEntity = (LivingEntity)entity; + if (!entity.isSpectator()) { +- livingEntity.addEffect( +- new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true) +- ); ++ //livingEntity.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true)); // Folia start - Fix off region raid heroes - moved down + if (livingEntity instanceof ServerPlayer serverPlayer) { +- serverPlayer.awardStat(Stats.RAID_WIN); +- CriteriaTriggers.RAID_WIN.trigger(serverPlayer); ++ //serverPlayer.awardStat(Stats.RAID_WIN); // Folia start - Fix off region raid heroes - moved down ++ //CriteriaTriggers.RAID_WIN.trigger(serverPlayer); // Folia start - Fix off region raid heroes - moved down + winners.add(serverPlayer.getBukkitEntity()); // CraftBukkit + } ++ // Folia start - Fix off region raid heroes ++ livingEntity.getBukkitEntity().taskScheduler.schedule((LivingEntity lv) -> { ++ lv.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true)); ++ if (lv instanceof ServerPlayer serverPlayer) { ++ serverPlayer.awardStat(Stats.RAID_WIN); ++ CriteriaTriggers.RAID_WIN.trigger(serverPlayer); ++ } ++ }, null, 1L); ++ // Folia end - Fix off region raid heroes + } + } + } +@@ -496,7 +510,7 @@ public class Raid { + Collection players = this.raidEvent.getPlayers(); + long randomLong = this.random.nextLong(); + +- for (ServerPlayer serverPlayer : this.level.players()) { ++ for (ServerPlayer serverPlayer : this.level.getLocalPlayers()) { // Folia - region threading + Vec3 vec3 = serverPlayer.position(); + Vec3 vec31 = Vec3.atCenterOf(pos); + double squareRoot = Math.sqrt((vec31.x - vec3.x) * (vec31.x - vec3.x) + (vec31.z - vec3.z) * (vec31.z - vec3.z)); +diff --git a/net/minecraft/world/entity/raid/Raider.java b/net/minecraft/world/entity/raid/Raider.java +index 8270d76a753bfd26a4c8ef6610bee5c24ee59cfe..7c385baae81b9a987c0e1e4deb017884600331bc 100644 +--- a/net/minecraft/world/entity/raid/Raider.java ++++ b/net/minecraft/world/entity/raid/Raider.java +@@ -86,7 +86,7 @@ public abstract class Raider extends PatrollingMonster { + Raid currentRaid = this.getCurrentRaid(); + if (this.canJoinRaid()) { + if (currentRaid == null) { +- if (this.level().getGameTime() % 20L == 0L) { ++ if (this.level().getRedstoneGameTime() % 20L == 0L) { // Folia - region threading + Raid raidAt = ((ServerLevel)this.level()).getRaidAt(this.blockPosition()); + if (raidAt != null && Raids.canJoinRaid(this, raidAt)) { + raidAt.joinRaid(raidAt.getGroupsSpawned(), this, null, true); +diff --git a/net/minecraft/world/entity/raid/Raids.java b/net/minecraft/world/entity/raid/Raids.java +index 34eb038725d1577f1a2d7c35c897b1270eac5749..0ffc1956d9e808871c5b36f6eb5ed750abaa880c 100644 +--- a/net/minecraft/world/entity/raid/Raids.java ++++ b/net/minecraft/world/entity/raid/Raids.java +@@ -25,9 +25,9 @@ import net.minecraft.world.phys.Vec3; + + public class Raids extends SavedData { + private static final String RAID_FILE_ID = "raids"; +- public final Map raidMap = Maps.newHashMap(); ++ public final Map raidMap = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - make raids thread-safe + private final ServerLevel level; +- private int nextAvailableID; ++ private final java.util.concurrent.atomic.AtomicInteger nextAvailableID = new java.util.concurrent.atomic.AtomicInteger(); // Folia - make raids thread-safe + private int tick; + + public static SavedData.Factory factory(ServerLevel level) { +@@ -36,7 +36,7 @@ public class Raids extends SavedData { + + public Raids(ServerLevel level) { + this.level = level; +- this.nextAvailableID = 1; ++ this.nextAvailableID.set(1); // Folia - make raids thread-safe + this.setDirty(); + } + +@@ -44,12 +44,25 @@ public class Raids extends SavedData { + return this.raidMap.get(id); + } + ++ // Folia start - make raids thread-safe ++ public void globalTick() { ++ ++this.tick; ++ if (this.tick % 200 == 0) { ++ this.setDirty(); ++ } ++ } ++ + public void tick() { +- this.tick++; ++ // Folia end - make raids thread-safe + Iterator iterator = this.raidMap.values().iterator(); + + while (iterator.hasNext()) { + Raid raid = iterator.next(); ++ // Folia start - make raids thread-safe ++ if (!raid.ownsRaid()) { ++ continue; ++ } ++ // Folia end - make raids thread-safe + if (this.level.getGameRules().getBoolean(GameRules.RULE_DISABLE_RAIDS)) { + raid.stop(); + } +@@ -62,14 +75,17 @@ public class Raids extends SavedData { + } + } + +- if (this.tick % 200 == 0) { +- this.setDirty(); +- } ++ // Folia - make raids thread-safe - move to globalTick() + + DebugPackets.sendRaids(this.level, this.raidMap.values()); + } + + public static boolean canJoinRaid(Raider raider, Raid raid) { ++ // Folia start - make raids thread-safe ++ if (!raid.ownsRaid()) { ++ return false; ++ } ++ // Folia end - make raids thread-safe + return raider != null + && raid != null + && raid.getLevel() != null +@@ -87,7 +103,7 @@ public class Raids extends SavedData { + return null; + } else { + DimensionType dimensionType = player.level().dimensionType(); +- if (!dimensionType.hasRaids()) { ++ if (!dimensionType.hasRaids() || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player) || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, pos.getX() >> 4, pos.getZ() >> 4, 8) || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, player.chunkPosition().x, player.chunkPosition().z, 8)) { // Folia - region threading + return null; + } else { + List list = this.level +@@ -145,7 +161,7 @@ public class Raids extends SavedData { + + public static Raids load(ServerLevel level, CompoundTag tag) { + Raids raids = new Raids(level); +- raids.nextAvailableID = tag.getInt("NextAvailableID"); ++ raids.nextAvailableID.set(tag.getInt("NextAvailableID")); // Folia - make raids thread-safe + raids.tick = tag.getInt("Tick"); + ListTag list = tag.getList("Raids", 10); + +@@ -160,7 +176,7 @@ public class Raids extends SavedData { + + @Override + public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { +- tag.putInt("NextAvailableID", this.nextAvailableID); ++ tag.putInt("NextAvailableID", this.nextAvailableID.get()); // Folia - make raids thread-safe + tag.putInt("Tick", this.tick); + ListTag listTag = new ListTag(); + +@@ -179,7 +195,7 @@ public class Raids extends SavedData { + } + + private int getUniqueId() { +- return ++this.nextAvailableID; ++ return this.nextAvailableID.incrementAndGet(); // Folia - make raids thread-safe + } + + @Nullable +@@ -188,6 +204,11 @@ public class Raids extends SavedData { + double d = distance; + + for (Raid raid1 : this.raidMap.values()) { ++ // Folia start - make raids thread-safe ++ if (!raid1.ownsRaid()) { ++ continue; ++ } ++ // Folia end - make raids thread-safe + double d1 = raid1.getCenter().distSqr(pos); + if (raid1.isActive() && d1 < d) { + raid = raid1; +diff --git a/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java b/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java +index 82421d3b4116ca406cdfffec5a3d65a99cbe294b..3a575ff4860c3b000a23e7754181f48d942441e9 100644 +--- a/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java ++++ b/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java +@@ -145,5 +145,11 @@ public class MinecartCommandBlock extends AbstractMinecart { + return net.minecraft.world.entity.vehicle.MinecartCommandBlock.this.getBukkitEntity(); + } + // CraftBukkit end ++ // Folia start ++ @Override ++ public void threadCheck() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(MinecartCommandBlock.this, "Asynchronous sendSystemMessage to a command block"); ++ } ++ // Folia end + } + } +diff --git a/net/minecraft/world/entity/vehicle/MinecartHopper.java b/net/minecraft/world/entity/vehicle/MinecartHopper.java +index 8341e7f01606fca90e69384c16fc19bb9e20d1b7..c07f6fefdba5242c09c0081a0f074948f9df9ae6 100644 +--- a/net/minecraft/world/entity/vehicle/MinecartHopper.java ++++ b/net/minecraft/world/entity/vehicle/MinecartHopper.java +@@ -145,7 +145,7 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper + + // Paper start + public void immunize() { +- this.activatedImmunityTick = Math.max(this.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 20); ++ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 20); + } + // Paper end + +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index 76f50437396f8f856381d0fbef52953ef7c263f6..d3b98f1d36b643989708ea22753a0c0d0d4243bc 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -386,31 +386,32 @@ public final class ItemStack implements DataComponentHolder { + DataComponentPatch previousPatch = this.components.asPatch(); + int oldCount = this.getCount(); + ServerLevel serverLevel = (ServerLevel) context.getLevel(); ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = serverLevel.getCurrentWorldData(); // Folia - region threading + + if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement +- serverLevel.captureBlockStates = true; ++ worldData.captureBlockStates = true; // Folia - region threading + // special case bonemeal + if (item == Items.BONE_MEAL) { +- serverLevel.captureTreeGeneration = true; ++ worldData.captureTreeGeneration = true; // Folia - region threading + } + } + InteractionResult interactionResult; + try { + interactionResult = item.useOn(context); + } finally { +- serverLevel.captureBlockStates = false; ++ worldData.captureBlockStates = false; // Folia - region threading + } + DataComponentPatch newPatch = this.components.asPatch(); + int newCount = this.getCount(); + this.setCount(oldCount); + this.restorePatch(previousPatch); +- if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) { +- serverLevel.captureTreeGeneration = false; ++ if (interactionResult.consumesAction() && worldData.captureTreeGeneration && !worldData.capturedBlockStates.isEmpty()) { // Folia - region threading ++ worldData.captureTreeGeneration = false; // Folia - region threading + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld()); +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; +- List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); +- serverLevel.capturedBlockStates.clear(); ++ 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 ++ List 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) { + boolean isBonemeal = this.getItem() == Items.BONE_MEAL; +@@ -436,15 +437,15 @@ public final class ItemStack implements DataComponentHolder { + player.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat + } + +- SignItem.openSign = null; // SPIGOT-6758 - Reset on early return ++ SignItem.openSign.set(null); // SPIGOT-6758 - Reset on early return // Folia - region threading + return interactionResult; + } +- serverLevel.captureTreeGeneration = false; ++ worldData.captureTreeGeneration = false; // Folia - region threading + if (player != null && interactionResult instanceof InteractionResult.Success success && success.wasItemInteraction()) { + InteractionHand hand = context.getHand(); + org.bukkit.event.block.BlockPlaceEvent placeEvent = null; +- List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); +- serverLevel.capturedBlockStates.clear(); ++ List blocks = new java.util.ArrayList<>(worldData.capturedBlockStates.values()); // Folia - region threading ++ worldData.capturedBlockStates.clear(); // Folia - region threading + if (blocks.size() > 1) { + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos.getX(), clickedPos.getY(), clickedPos.getZ()); + } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement +@@ -455,17 +456,17 @@ public final class ItemStack implements DataComponentHolder { + interactionResult = InteractionResult.FAIL; // cancel placement + // PAIL: Remove this when MC-99075 fixed + placeEvent.getPlayer().updateInventory(); +- serverLevel.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot ++ worldData.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot // Folia - region threading + // revert back all captured blocks +- serverLevel.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 +- serverLevel.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent ++ worldData.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 // Folia - region threading ++ worldData.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent // Folia - region threading + for (org.bukkit.block.BlockState blockstate : blocks) { + blockstate.update(true, false); + } +- serverLevel.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent +- serverLevel.preventPoiUpdated = false; ++ worldData.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent // Folia - region threading ++ worldData.preventPoiUpdated = false; // Folia - region threading + +- SignItem.openSign = null; // SPIGOT-6758 - Reset on early return ++ SignItem.openSign.set(null); // SPIGOT-6758 - Reset on early return // Folia - region threading + } else { + // Change the stack to its new contents if it hasn't been tampered with. + if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), previousPatch)) { +@@ -473,7 +474,7 @@ public final class ItemStack implements DataComponentHolder { + this.setCount(newCount); + } + +- for (java.util.Map.Entry e : serverLevel.capturedTileEntities.entrySet()) { ++ for (java.util.Map.Entry e : worldData.capturedTileEntities.entrySet()) { // Folia - region threading + serverLevel.setBlockEntity(e.getValue()); + } + +@@ -508,15 +509,15 @@ public final class ItemStack implements DataComponentHolder { + } + + // SPIGOT-4678 +- if (this.item instanceof SignItem && SignItem.openSign != null) { ++ if (this.item instanceof SignItem && SignItem.openSign.get() != null) { // Folia - region threading + try { +- if (serverLevel.getBlockEntity(SignItem.openSign) instanceof net.minecraft.world.level.block.entity.SignBlockEntity blockEntity) { +- if (serverLevel.getBlockState(SignItem.openSign).getBlock() instanceof net.minecraft.world.level.block.SignBlock signBlock) { ++ if (serverLevel.getBlockEntity(SignItem.openSign.get()) instanceof net.minecraft.world.level.block.entity.SignBlockEntity blockEntity) { // Folia - region threading ++ if (serverLevel.getBlockState(SignItem.openSign.get()).getBlock() instanceof net.minecraft.world.level.block.SignBlock signBlock) { // Folia - region threading + signBlock.openTextEdit(player, blockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // CraftBukkit // Paper - Add PlayerOpenSignEvent + } + } + } finally { +- SignItem.openSign = null; ++ SignItem.openSign.set(null); + } + } + +@@ -544,8 +545,8 @@ public final class ItemStack implements DataComponentHolder { + player.awardStat(Stats.ITEM_USED.get(item)); + } + } +- serverLevel.capturedTileEntities.clear(); +- serverLevel.capturedBlockStates.clear(); ++ worldData.capturedTileEntities.clear(); // Folia - region threading ++ worldData.capturedBlockStates.clear(); // Folia - region threading + // CraftBukkit end + + return interactionResult; +diff --git a/net/minecraft/world/item/MapItem.java b/net/minecraft/world/item/MapItem.java +index 8795d54cff569c911e0a535f38a0ec4130f7b4d5..9f07ce560e265582eec0fff5877a923f62a60e13 100644 +--- a/net/minecraft/world/item/MapItem.java ++++ b/net/minecraft/world/item/MapItem.java +@@ -70,6 +70,7 @@ public class MapItem extends Item { + } + + public void update(Level level, Entity viewer, MapItemSavedData data) { ++ synchronized (data) { // Folia - make map data thread-safe + if (level.dimension() == data.dimension && viewer instanceof Player) { + int i = 1 << data.scale; + int i1 = data.centerX; +@@ -99,8 +100,8 @@ public class MapItem extends Item { + int i9 = (i1 / i + i6 - 64) * i; + int i10 = (i2 / i + i7 - 64) * i; + Multiset multiset = LinkedHashMultiset.create(); +- LevelChunk chunk = level.getChunkIfLoaded(SectionPos.blockToSectionCoord(i9), SectionPos.blockToSectionCoord(i10)); // Paper - Maps shouldn't load chunks +- if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks ++ LevelChunk chunk = level.getChunkIfLoaded(SectionPos.blockToSectionCoord(i9), SectionPos.blockToSectionCoord(i10)); // Paper - Maps shouldn't load chunks // Folia - super important that it uses getChunkIfLoaded ++ if (chunk != null && !chunk.isEmpty() && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, chunk.getPos())) { // Paper - Maps shouldn't load chunks // Folia - make sure chunk is owned + int i11 = 0; + double d1 = 0.0; + if (level.dimensionType().hasCeiling()) { +@@ -182,6 +183,7 @@ public class MapItem extends Item { + } + } + } ++ } // Folia - make map data thread-safe + } + + private BlockState getCorrectStateForFluidBlock(Level level, BlockState state, BlockPos pos) { +@@ -196,6 +198,7 @@ public class MapItem extends Item { + public static void renderBiomePreviewMap(ServerLevel serverLevel, ItemStack stack) { + MapItemSavedData savedData = getSavedData(stack, serverLevel); + if (savedData != null) { ++ synchronized (savedData) { // Folia - make map data thread-safe + if (serverLevel.dimension() == savedData.dimension) { + int i = 1 << savedData.scale; + int i1 = savedData.centerX; +@@ -265,6 +268,7 @@ public class MapItem extends Item { + } + } + } ++ } // Folia - make map data thread-safe + } + } + +@@ -273,6 +277,7 @@ public class MapItem extends Item { + if (!level.isClientSide) { + MapItemSavedData savedData = getSavedData(stack, level); + if (savedData != null) { ++ synchronized (savedData) { // Folia - region threading + if (entity instanceof Player player) { + savedData.tickCarriedBy(player, stack); + } +@@ -280,6 +285,7 @@ public class MapItem extends Item { + if (!savedData.locked && (isSelected || entity instanceof Player && ((Player)entity).getOffhandItem() == stack)) { + this.update(level, entity, savedData); + } ++ } // Folia - region threading + } + } + } +diff --git a/net/minecraft/world/item/SignItem.java b/net/minecraft/world/item/SignItem.java +index fffac12db30d4321981959a9149cc56f8b4f6df6..fdf4fa92a5ca98fae6266e29a54fb1b77e69407c 100644 +--- a/net/minecraft/world/item/SignItem.java ++++ b/net/minecraft/world/item/SignItem.java +@@ -11,7 +11,7 @@ import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.block.state.BlockState; + + public class SignItem extends StandingAndWallBlockItem { +- public static BlockPos openSign; // CraftBukkit ++ public static final ThreadLocal openSign = new ThreadLocal<>(); // CraftBukkit // Folia - region threading + public SignItem(Block standingBlock, Block wallBlock, Item.Properties properties) { + super(standingBlock, wallBlock, Direction.DOWN, properties); + } +@@ -30,7 +30,7 @@ public class SignItem extends StandingAndWallBlockItem { + && level.getBlockState(pos).getBlock() instanceof SignBlock signBlock) { + // CraftBukkit start - SPIGOT-4678 + // signBlock.openTextEdit(player, signBlockEntity, true); +- SignItem.openSign = pos; ++ SignItem.openSign.set(pos); // Folia - region threading + // CraftBukkit end + } + +diff --git a/net/minecraft/world/item/component/LodestoneTracker.java b/net/minecraft/world/item/component/LodestoneTracker.java +index 0c00c23743a4978e8dceed5bbee8ca44b0e0c8d6..b6de5d017bed5c71125f26881b95383386aa1a79 100644 +--- a/net/minecraft/world/item/component/LodestoneTracker.java ++++ b/net/minecraft/world/item/component/LodestoneTracker.java +@@ -29,7 +29,10 @@ public record LodestoneTracker(Optional target, boolean tracked) { + return this; + } else { + BlockPos blockPos = this.target.get().pos(); +- return level.isInWorldBounds(blockPos) && (!level.hasChunkAt(blockPos) || level.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)) // Paper - Prevent compass from loading chunks ++ // Folia start - do not access the POI data off-region ++ net.minecraft.world.level.chunk.LevelChunk chunk = level.getChunkIfLoaded(blockPos); ++ return level.isInWorldBounds(blockPos) && (chunk == null || chunk.getBlockState(blockPos).getBlock() == net.minecraft.world.level.block.Blocks.LODESTONE) // Paper - Prevent compass from loading chunks ++ // Folia end - do not access the POI data off-region + ? this + : new LodestoneTracker(Optional.empty(), true); + } +diff --git a/net/minecraft/world/level/BaseCommandBlock.java b/net/minecraft/world/level/BaseCommandBlock.java +index a67d40eb4bfa85888af8bf027a8859378d290cfa..b02b79ccedb8b87bc22270377dfc36e21ebe1724 100644 +--- a/net/minecraft/world/level/BaseCommandBlock.java ++++ b/net/minecraft/world/level/BaseCommandBlock.java +@@ -21,7 +21,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.phys.Vec3; + + public abstract class BaseCommandBlock implements CommandSource { +- private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); ++ private static final ThreadLocal TIME_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss")); // Folia - region threading - SDF is not thread-safe + private static final Component DEFAULT_NAME = Component.literal("@"); + private long lastExecution = -1L; + private boolean updateLastExecution = true; +@@ -114,6 +114,7 @@ public abstract class BaseCommandBlock implements CommandSource { + } + + public boolean performCommand(Level level) { ++ if (true) return false; // Folia - region threading + if (level.isClientSide || level.getGameTime() == this.lastExecution) { + return false; + } else if ("Searge".equalsIgnoreCase(this.command)) { +@@ -164,11 +165,14 @@ public abstract class BaseCommandBlock implements CommandSource { + this.customName = customName; + } + ++ public void threadCheck() {} // Folia ++ + @Override + public void sendSystemMessage(Component component) { + if (this.trackOutput) { + org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks +- this.lastOutput = Component.literal("[" + TIME_FORMAT.format(new Date()) + "] ").append(component); ++ this.threadCheck(); // Folia ++ this.lastOutput = Component.literal("[" + TIME_FORMAT.get().format(new Date()) + "] ").append(component); // Folia - region threading - SDF is not thread-safe + this.onUpdated(); + } + } +diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java +index 892a7c1eb1b321ca6d5ca709142e7feae1220815..c4c7993714ff4189b05e2b4653fd2a5eba6b7da9 100644 +--- a/net/minecraft/world/level/EntityGetter.java ++++ b/net/minecraft/world/level/EntityGetter.java +@@ -24,6 +24,12 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst + return this.getEntities(EntityTypeTest.forClass(entityClass), area, filter); + } + ++ // Folia start - region threading ++ default List getLocalPlayers() { ++ return java.util.Collections.emptyList(); ++ } ++ // Folia end - region threading ++ + List players(); + + default List getEntities(@Nullable Entity entity, AABB area) { +@@ -123,7 +129,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst + double d = -1.0; + Player player = null; + +- for (Player player1 : this.players()) { ++ for (Player player1 : this.getLocalPlayers()) { // Folia - region threading + if (predicate == null || predicate.test(player1)) { + double d1 = player1.distanceToSqr(x, y, z); + if ((distance < 0.0 || d1 < distance * distance) && (d == -1.0 || d1 < d)) { +@@ -144,7 +150,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst + default List findNearbyBukkitPlayers(double x, double y, double z, double radius, @Nullable Predicate predicate) { + com.google.common.collect.ImmutableList.Builder builder = com.google.common.collect.ImmutableList.builder(); + +- for (Player human : this.players()) { ++ for (Player human : this.getLocalPlayers()) { // Folia - region threading + if (predicate == null || predicate.test(human)) { + double distanceSquared = human.distanceToSqr(x, y, z); + +@@ -171,7 +177,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst + + // Paper start - Affects Spawning API + default boolean hasNearbyAlivePlayerThatAffectsSpawning(double x, double y, double z, double range) { +- for (Player player : this.players()) { ++ for (Player player : this.getLocalPlayers()) { // Folia - region threading + if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player)) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check + double distanceSqr = player.distanceToSqr(x, y, z); + if (range < 0.0D || distanceSqr < range * range) { +@@ -184,7 +190,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst + // Paper end - Affects Spawning API + + default boolean hasNearbyAlivePlayer(double x, double y, double z, double distance) { +- for (Player player : this.players()) { ++ for (Player player : this.getLocalPlayers()) { // Folia - region threading + if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { + double d = player.distanceToSqr(x, y, z); + if (distance < 0.0 || d < distance * distance) { +@@ -198,8 +204,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst + + @Nullable + default Player getPlayerByUUID(UUID uniqueId) { +- for (int i = 0; i < this.players().size(); i++) { +- Player player = this.players().get(i); ++ for (Player player : this.getLocalPlayers()) { // Folia - region threading + if (uniqueId.equals(player.getUUID())) { + return player; + } +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 1dbe7c7c1051c3972105534a07ce50d4cf98fc85..db4ce98706bf69dcd8144faba1780f83ca1f6787 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -115,10 +115,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public static final int TICKS_PER_DAY = 24000; + public static final int MAX_ENTITY_SPAWN_Y = 20000000; + public static final int MIN_ENTITY_SPAWN_Y = -20000000; +- public final List blockEntityTickers = Lists.newArrayList(); // Paper - public +- protected final NeighborUpdater neighborUpdater; +- private final List pendingBlockEntityTickers = Lists.newArrayList(); +- private boolean tickingBlockEntities; ++ //public final List blockEntityTickers = Lists.newArrayList(); // Paper - public // Folia - region threading ++ public final int neighbourUpdateMax; //protected final NeighborUpdater neighborUpdater; // Folia - region threading ++ //private final List pendingBlockEntityTickers = Lists.newArrayList(); // Folia - region threading ++ //private boolean tickingBlockEntities; // Folia - region threading + public final Thread thread; + private final boolean isDebug; + private int skyDarken; +@@ -128,7 +128,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public float rainLevel; + protected float oThunderLevel; + public float thunderLevel; +- public final RandomSource random = new ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // Paper - replace random ++ public final RandomSource random = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Paper - replace random // Folia - region threading + @Deprecated + private final RandomSource threadSafeRandom = RandomSource.createThreadSafe(); + private final Holder dimensionTypeRegistration; +@@ -139,28 +139,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + private final ResourceKey dimension; + private final RegistryAccess registryAccess; + private final DamageSources damageSources; +- private long subTickCount; ++ private final java.util.concurrent.atomic.AtomicLong subTickCount = new java.util.concurrent.atomic.AtomicLong(); //private long subTickCount; // Folia - region threading + + // CraftBukkit start Added the following + private final CraftWorld world; + public boolean pvpMode; + public org.bukkit.generator.ChunkGenerator generator; + +- 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 Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper +- public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates +- public List captureDrops; ++ // Folia - region threading - moved to regionised data + public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); +- // Paper start +- public int wakeupInactiveRemainingAnimals; +- public int wakeupInactiveRemainingFlying; +- public int wakeupInactiveRemainingMonsters; +- public int wakeupInactiveRemainingVillagers; +- // Paper end +- public boolean populating; ++ // Folia - region threading - moved to regionised data ++ // Folia - region threading + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + // Paper start - add paper world config + private final io.papermc.paper.configuration.WorldConfiguration paperConfig; +@@ -173,9 +162,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +- private int tileTickPosition; +- public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions +- public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here ++ //private int tileTickPosition; // Folia - region threading ++ //public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions // Folia - region threading ++ //public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here // Folia - region threading + + public CraftWorld getWorld() { + return this.world; +@@ -825,6 +814,32 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z); + } + // Paper end - optimise random ticking ++ // Folia start - region ticking ++ public final io.papermc.paper.threadedregions.RegionizedData worldRegionData ++ = new io.papermc.paper.threadedregions.RegionizedData<>( ++ (ServerLevel)this, () -> new io.papermc.paper.threadedregions.RegionizedWorldData((ServerLevel)Level.this), ++ io.papermc.paper.threadedregions.RegionizedWorldData.REGION_CALLBACK ++ ); ++ public volatile io.papermc.paper.threadedregions.RegionizedServer.WorldLevelData tickData; ++ public final java.util.concurrent.ConcurrentHashMap.KeySetView needsChangeBroadcasting = java.util.concurrent.ConcurrentHashMap.newKeySet(); ++ ++ public io.papermc.paper.threadedregions.RegionizedWorldData getCurrentWorldData() { ++ final io.papermc.paper.threadedregions.RegionizedWorldData ret = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); ++ if (ret == null) { ++ return ret; ++ } ++ Level world = ret.world; ++ if (world != this) { ++ throw new IllegalStateException("World mismatch: expected " + this.getWorld().getName() + " but got " + world.getWorld().getName()); ++ } ++ return ret; ++ } ++ ++ @Override ++ public List getLocalPlayers() { ++ return this.getCurrentWorldData().getLocalPlayers(); ++ } ++ // Folia end - region ticking + + protected Level( + WritableLevelData levelData, +@@ -888,7 +903,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + this.thread = Thread.currentThread(); + this.biomeManager = new BiomeManager(this, biomeZoomSeed); + this.isDebug = isDebug; +- this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates); ++ this.neighbourUpdateMax = maxChainedNeighborUpdates; // Folia - region threading + this.registryAccess = registryAccess; + this.damageSources = new DamageSources(registryAccess); + +@@ -1035,8 +1050,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + @Nullable + public final BlockState getBlockStateIfLoaded(BlockPos pos) { + // CraftBukkit start - tree generation +- if (this.captureTreeGeneration) { +- CraftBlockState previous = this.capturedBlockStates.get(pos); ++ if (this.getCurrentWorldData().captureTreeGeneration) { // Folia - region threading ++ CraftBlockState previous = this.getCurrentWorldData().capturedBlockStates.get(pos); // Folia - region threading + if (previous != null) { + return previous.getHandle(); + } +@@ -1098,16 +1113,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // Folia - region threading ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.getCurrentWorldData(); // Folia - region threading + // CraftBukkit start - tree generation +- if (this.captureTreeGeneration) { ++ if (worldData.captureTreeGeneration) { // Folia - region threading + // Paper start - Protect Bedrock and End Portal/Frames from being destroyed + BlockState type = getBlockState(pos); + if (!type.isDestroyable()) return false; + // Paper end - Protect Bedrock and End Portal/Frames from being destroyed +- CraftBlockState blockstate = this.capturedBlockStates.get(pos); ++ CraftBlockState blockstate = worldData.capturedBlockStates.get(pos); // Folia - region threading + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); +- this.capturedBlockStates.put(pos.immutable(), blockstate); ++ worldData.capturedBlockStates.put(pos.immutable(), blockstate); // Folia - region threading + } + blockstate.setData(state); + blockstate.setFlag(flags); +@@ -1123,10 +1140,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + Block block = state.getBlock(); + // CraftBukkit start - capture blockstates + boolean captured = false; +- if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) { ++ if (worldData.captureBlockStates && !worldData.capturedBlockStates.containsKey(pos)) { // Folia - region threading + CraftBlockState blockstate = (CraftBlockState) world.getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); // Paper - use CB getState to get a suitable snapshot + blockstate.setFlag(flags); // Paper - set flag +- this.capturedBlockStates.put(pos.immutable(), blockstate); ++ worldData.capturedBlockStates.put(pos.immutable(), blockstate); // Folia - region threading + captured = true; + } + // CraftBukkit end +@@ -1136,8 +1153,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + if (blockState == null) { + // CraftBukkit start - remove blockstate if failed (or the same) +- if (this.captureBlockStates && captured) { +- this.capturedBlockStates.remove(pos); ++ if (worldData.captureBlockStates && captured) { // Folia - region threading ++ worldData.capturedBlockStates.remove(pos); // Folia - region threading + } + // CraftBukkit end + return false; +@@ -1174,7 +1191,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + */ + + // CraftBukkit start +- if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates ++ if (!worldData.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates // Folia - region threading + // Modularize client and physic updates + // Spigot start + try { +@@ -1219,7 +1236,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam + CraftWorld world = ((ServerLevel) this).getWorld(); + boolean cancelledUpdates = false; // Paper - Fix block place logic +- if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent ++ if (world != null && ((ServerLevel)this).getCurrentWorldData().hasPhysicsEvent) { // Paper - BlockPhysicsEvent // Folia - region threading + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); + this.getCraftServer().getPluginManager().callEvent(event); + +@@ -1233,7 +1250,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + + // CraftBukkit start - SPIGOT-5710 +- if (!this.preventPoiUpdated) { ++ if (!this.getCurrentWorldData().preventPoiUpdated) { // Folia - region threading + this.onBlockStateChange(blockposition, iblockdata1, iblockdata2); + } + // CraftBukkit end +@@ -1322,7 +1339,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public void neighborShapeChanged(Direction direction, BlockPos pos, BlockPos neighborPos, BlockState neighborState, int flags, int recursionLeft) { +- this.neighborUpdater.shapeUpdate(direction, neighborState, pos, neighborPos, flags, recursionLeft); ++ this.getCurrentWorldData().neighborUpdater.shapeUpdate(direction, neighborState, pos, neighborPos, flags, recursionLeft); // Folia - region threading + } + + @Override +@@ -1346,11 +1363,34 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + return this.getChunkSource().getLightEngine(); + } + ++ // Folia start - region threading ++ @Nullable ++ public BlockState getBlockStateFromEmptyChunkIfLoaded(BlockPos pos) { ++ net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)this.getChunkSource(); ++ ChunkAccess chunk = chunkProvider.getChunkAtImmediately(pos.getX() >> 4, pos.getZ() >> 4); ++ if (chunk != null) { ++ return chunk.getBlockState(pos); ++ } ++ return null; ++ } ++ ++ @Nullable ++ public BlockState getBlockStateFromEmptyChunk(BlockPos pos) { ++ net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)this.getChunkSource(); ++ ChunkAccess chunk = chunkProvider.getChunkAtImmediately(pos.getX() >> 4, pos.getZ() >> 4); ++ if (chunk != null) { ++ return chunk.getBlockState(pos); ++ } ++ chunk = chunkProvider.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.EMPTY, true); ++ return chunk.getBlockState(pos); ++ } ++ // Folia end - region threading ++ + @Override + public BlockState getBlockState(BlockPos pos) { + // CraftBukkit start - tree generation +- if (this.captureTreeGeneration) { +- CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper ++ if (this.getCurrentWorldData().captureTreeGeneration) { // Folia - region threading ++ CraftBlockState previous = this.getCurrentWorldData().capturedBlockStates.get(pos); // Paper // Folia - region threading + if (previous != null) { + return previous.getHandle(); + } +@@ -1454,17 +1494,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + + public void addBlockEntityTicker(TickingBlockEntity ticker) { +- (this.tickingBlockEntities ? this.pendingBlockEntityTickers : this.blockEntityTickers).add(ticker); ++ ((ServerLevel)this).getCurrentWorldData().addBlockEntityTicker(ticker); // Folia - regionised ticking + } + + protected void tickBlockEntities() { + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("blockEntities"); +- this.tickingBlockEntities = true; +- if (!this.pendingBlockEntityTickers.isEmpty()) { +- this.blockEntityTickers.addAll(this.pendingBlockEntityTickers); +- this.pendingBlockEntityTickers.clear(); +- } ++ final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - regionised ticking ++ regionizedWorldData.seTtickingBlockEntities(true); // Folia - regionised ticking ++ regionizedWorldData.pushPendingTickingBlockEntities(); // Folia - regionised ticking ++ List blockEntityTickers = regionizedWorldData.getBlockEntityTickers(); // Folia - regionised ticking + + // Spigot start + boolean runsNormally = this.tickRateManager().runsNormally(); +@@ -1472,9 +1511,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + int tickedEntities = 0; // Paper - rewrite chunk system + var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet(); // Paper - Fix MC-117075; use removeAll + toRemove.add(null); // Paper - Fix MC-117075 +- for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters +- this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0; +- TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition); ++ for (int i = 0; i < blockEntityTickers.size(); i++) { // Paper - Disable tick limiters // Folia - regionised ticking ++ TickingBlockEntity tickingBlockEntity = blockEntityTickers.get(i); // Folia - regionised ticking + // Spigot end + if (tickingBlockEntity.isRemoved()) { + toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll +@@ -1487,11 +1525,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + // Paper end - rewrite chunk system + } + } +- this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 ++ blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 // Folia - regionised ticking + +- this.tickingBlockEntities = false; ++ regionizedWorldData.seTtickingBlockEntities(false); // Folia - regionised ticking + profilerFiller.pop(); +- this.spigotConfig.currentPrimedTnt = 0; // Spigot ++ regionizedWorldData.currentPrimedTnt = 0; // Spigot // Folia - region threading + } + + public void guardEntityTick(Consumer consumerEntity, T entity) { +@@ -1502,7 +1540,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); + MinecraftServer.LOGGER.error(msg, var6); + getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var6))); // Paper - ServerExceptionEvent +- entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ if (!(entity instanceof net.minecraft.server.level.ServerPlayer)) entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Folia - properly disconnect players ++ if (entity instanceof net.minecraft.server.level.ServerPlayer player) player.connection.disconnect(net.minecraft.network.chat.Component.translatable("multiplayer.disconnect.generic"), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Folia - properly disconnect players + // Paper end - Prevent block entity and entity crashes + } + this.moonrise$midTickTasks(); // Paper - rewrite chunk system +@@ -1648,9 +1687,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Nullable + public BlockEntity getBlockEntity(BlockPos pos, boolean validate) { ++ // Folia start - region threading ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { ++ return null; ++ } ++ // Folia end - region threading + // Paper start - Perf: Optimize capturedTileEntities lookup + net.minecraft.world.level.block.entity.BlockEntity blockEntity; +- if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { ++ if (!this.getCurrentWorldData().capturedTileEntities.isEmpty() && (blockEntity = this.getCurrentWorldData().capturedTileEntities.get(pos)) != null) { // Folia - region threading + return blockEntity; + } + // Paper end - Perf: Optimize capturedTileEntities lookup +@@ -1668,8 +1712,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + BlockPos blockPos = blockEntity.getBlockPos(); + if (!this.isOutsideBuildHeight(blockPos)) { + // CraftBukkit start +- if (this.captureBlockStates) { +- this.capturedTileEntities.put(blockPos.immutable(), blockEntity); ++ if (this.getCurrentWorldData().captureBlockStates) { // Folia - region threading ++ this.getCurrentWorldData().capturedTileEntities.put(blockPos.immutable(), blockEntity); // Folia - region threading + return; + } + // CraftBukkit end +@@ -1749,6 +1793,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // Folia - region threading + Profiler.get().incrementCounter("getEntities"); + List list = Lists.newArrayList(); + +@@ -1778,6 +1823,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public void getEntities(final EntityTypeTest entityTypeTest, + final AABB boundingBox, final Predicate predicate, + final List into, final int maxCount) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, boundingBox, "Cannot getEntities asynchronously"); // Folia - region threading + Profiler.get().incrementCounter("getEntities"); + + if (entityTypeTest instanceof net.minecraft.world.entity.EntityType byType) { +@@ -1877,13 +1923,34 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public void disconnect() { + } + ++ @Override // Folia - region threading + public long getGameTime() { +- return this.levelData.getGameTime(); ++ // Folia start - region threading ++ // Dumb world gen thread calls this for some reason. So, check for null. ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.getCurrentWorldData(); ++ return worldData == null ? this.getLevelData().getGameTime() : worldData.getTickData().nonRedstoneGameTime(); ++ // Folia end - region threading + } + + public long getDayTime() { +- return this.levelData.getDayTime(); ++ // Folia start - region threading ++ // Dumb world gen thread calls this for some reason. So, check for null. ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.getCurrentWorldData(); ++ return worldData == null ? this.getLevelData().getDayTime() : worldData.getTickData().dayTime(); ++ // Folia end - region threading ++ } ++ ++ // Folia start - region threading ++ @Override ++ public long dayTime() { ++ return this.getDayTime(); ++ } ++ ++ @Override ++ public long getRedstoneGameTime() { ++ return this.getCurrentWorldData().getRedstoneGameTime(); + } ++ // Folia end - region threading + + public boolean mayInteract(Player player, BlockPos pos) { + return true; +@@ -2061,8 +2128,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public abstract RecipeAccess recipeAccess(); + + public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) { +- this.randValue = this.randValue * 3 + 1013904223; +- int i = this.randValue >> 2; ++ int i = this.random.nextInt() >> 2; // Folia - region threading + return new BlockPos(x + (i & 15), y + (i >> 16 & yMask), z + (i >> 8 & 15)); + } + +@@ -2083,7 +2149,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public long nextSubTickCount() { +- return this.subTickCount++; ++ return this.subTickCount.getAndIncrement(); // Folia - region threading + } + + @Override +diff --git a/net/minecraft/world/level/LevelAccessor.java b/net/minecraft/world/level/LevelAccessor.java +index ee9d320da1b4c3aa66be6592867e95c706b65b3a..cd5bfa374b0b1af64bc8415ace94fa43955e5145 100644 +--- a/net/minecraft/world/level/LevelAccessor.java ++++ b/net/minecraft/world/level/LevelAccessor.java +@@ -33,14 +33,24 @@ public interface LevelAccessor extends CommonLevelAccessor, LevelTimeAccess, Sch + + long nextSubTickCount(); + ++ // Folia start - region threading ++ default long getGameTime() { ++ return this.getLevelData().getGameTime(); ++ } ++ ++ default long getRedstoneGameTime() { ++ return this.getLevelData().getGameTime(); ++ } ++ // Folia end - region threading ++ + @Override + default ScheduledTick createTick(BlockPos pos, T type, int delay, TickPriority priority) { +- return new ScheduledTick<>(type, pos, this.getLevelData().getGameTime() + delay, priority, this.nextSubTickCount()); ++ return new ScheduledTick<>(type, pos, this.getRedstoneGameTime() + delay, priority, this.nextSubTickCount()); // Folia - region threading + } + + @Override + default ScheduledTick createTick(BlockPos pos, T type, int delay) { +- return new ScheduledTick<>(type, pos, this.getLevelData().getGameTime() + delay, this.nextSubTickCount()); ++ return new ScheduledTick<>(type, pos, this.getRedstoneGameTime() + delay, this.nextSubTickCount()); // Folia - region threading + } + + LevelData getLevelData(); +diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java +index 26c8c1e5598daf3550aef05b12218c47bda6618b..e59e1bb91e446406e58cc8046a85b693adb11e86 100644 +--- a/net/minecraft/world/level/LevelReader.java ++++ b/net/minecraft/world/level/LevelReader.java +@@ -204,6 +204,25 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste + return toY >= this.getMinY() && fromY <= this.getMaxY() && this.hasChunksAt(fromX, fromZ, toX, toZ); + } + ++ // Folia start - region threading ++ default boolean hasAndOwnsChunksAt(int minX, int minZ, int maxX, int maxZ) { ++ int i = SectionPos.blockToSectionCoord(minX); ++ int j = SectionPos.blockToSectionCoord(maxX); ++ int k = SectionPos.blockToSectionCoord(minZ); ++ int l = SectionPos.blockToSectionCoord(maxZ); ++ ++ for(int m = i; m <= j; ++m) { ++ for(int n = k; n <= l; ++n) { ++ if (!this.hasChunk(m, n) || (this instanceof net.minecraft.server.level.ServerLevel world && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(world, m, n))) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ // Folia end - region threading ++ + @Deprecated + default boolean hasChunksAt(int fromX, int fromZ, int toX, int toZ) { + int sectionPosCoord = SectionPos.blockToSectionCoord(fromX); +diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java +index 17ce115e887cbbb06ad02ab7ddb488e27342c0e4..5ce81eafee33d22b69029c088d4be497131338a2 100644 +--- a/net/minecraft/world/level/NaturalSpawner.java ++++ b/net/minecraft/world/level/NaturalSpawner.java +@@ -137,7 +137,7 @@ public final class NaturalSpawner { + int limit = mobCategory.getMaxInstancesPerChunk(); + SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory); + if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { +- spawnThisTick = level.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(spawnCategory) == 0; ++ spawnThisTick = level.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && level.getRedstoneGameTime() % level.ticksPerSpawnCategory.getLong(spawnCategory) == 0; // Folia - region threading + limit = level.getWorld().getSpawnLimit(spawnCategory); + } + +diff --git a/net/minecraft/world/level/ServerExplosion.java b/net/minecraft/world/level/ServerExplosion.java +index c4485f28def66264846a436cfba7bddccb66b82e..73456d625489302e28c0452bde4508db0efa126c 100644 +--- a/net/minecraft/world/level/ServerExplosion.java ++++ b/net/minecraft/world/level/ServerExplosion.java +@@ -773,17 +773,18 @@ public class ServerExplosion implements Explosion { + if (!this.level.paperConfig().environment.optimizeExplosions) { + return this.getSeenFraction(vec3d, entity, this.directMappedBlockCache, this.mutablePos); // Paper - collision optimisations + } ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.level.getCurrentWorldData(); // Folia - region threading + CacheKey key = new CacheKey(this, entity.getBoundingBox()); +- Float blockDensity = this.level.explosionDensityCache.get(key); ++ Float blockDensity = worldData.explosionDensityCache.get(key); // Folia - region threading + if (blockDensity == null) { + blockDensity = this.getSeenFraction(vec3d, entity, this.directMappedBlockCache, this.mutablePos); // Paper - collision optimisations +- this.level.explosionDensityCache.put(key, blockDensity); ++ worldData.explosionDensityCache.put(key, blockDensity); // Folia - region threading + } + + return blockDensity; + } + +- static class CacheKey { ++ public static class CacheKey { // Folia - region threading - public + private final Level world; + private final double posX, posY, posZ; + private final double minX, minY, minZ; +diff --git a/net/minecraft/world/level/ServerLevelAccessor.java b/net/minecraft/world/level/ServerLevelAccessor.java +index b4f14ff9ef0c212f4d0e0c2ccf20ce1e7af9e734..441ba6ae8885a968734ac0abdb8a9d09fa658430 100644 +--- a/net/minecraft/world/level/ServerLevelAccessor.java ++++ b/net/minecraft/world/level/ServerLevelAccessor.java +@@ -6,6 +6,12 @@ import net.minecraft.world.entity.Entity; + public interface ServerLevelAccessor extends LevelAccessor { + ServerLevel getLevel(); + ++ // Folia start - region threading ++ default public StructureManager structureManager() { ++ throw new UnsupportedOperationException(); ++ } ++ // Folia end - region threading ++ + default void addFreshEntityWithPassengers(Entity entity) { + // CraftBukkit start + this.addFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); +diff --git a/net/minecraft/world/level/StructureManager.java b/net/minecraft/world/level/StructureManager.java +index 8bc6a6c86cd8db53feefba7508b6031ba67e242e..9abfcfa3e8d8319e98866b2a81f2eb9ac7269055 100644 +--- a/net/minecraft/world/level/StructureManager.java ++++ b/net/minecraft/world/level/StructureManager.java +@@ -48,12 +48,7 @@ public class StructureManager { + } + + public List startsForStructure(ChunkPos chunkPos, Predicate structurePredicate) { +- // Paper start - Fix swamp hut cat generation deadlock +- return this.startsForStructure(chunkPos, structurePredicate, null); +- } +- +- public List startsForStructure(ChunkPos chunkPos, Predicate structurePredicate, @Nullable ServerLevelAccessor levelAccessor) { +- Map allReferences = (levelAccessor == null ? this.level : levelAccessor).getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); ++ Map allReferences = this.level.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); // Folia - region threading + // Paper end - Fix swamp hut cat generation deadlock + Builder builder = ImmutableList.builder(); + +@@ -124,20 +119,12 @@ public class StructureManager { + } + + public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate) { +- // Paper start - Fix swamp hut cat generation deadlock +- return this.getStructureWithPieceAt(pos, predicate, null); +- } +- +- public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey tag, @Nullable ServerLevelAccessor levelAccessor) { +- return this.getStructureWithPieceAt(pos, structure -> structure.is(tag), levelAccessor); +- } +- +- public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate, @Nullable ServerLevelAccessor levelAccessor) { ++ // Folia - region threading + // Paper end - Fix swamp hut cat generation deadlock + Registry registry = this.registryAccess().lookupOrThrow(Registries.STRUCTURE); + + for (StructureStart structureStart : this.startsForStructure( +- new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false), levelAccessor // Paper - Fix swamp hut cat generation deadlock ++ new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false) // Paper - Fix swamp hut cat generation deadlock // Folia - region threading + )) { + if (this.structureHasPieceAt(pos, structureStart)) { + return structureStart; +@@ -182,7 +169,7 @@ public class StructureManager { + } + + public void addReference(StructureStart structureStart) { +- structureStart.addReference(); ++ //structureStart.addReference(); // Folia - region threading - move to caller + this.structureCheck.incrementReference(structureStart.getChunkPos(), structureStart.getStructure()); + } + +diff --git a/net/minecraft/world/level/block/BedBlock.java b/net/minecraft/world/level/block/BedBlock.java +index 8c21e8aa4922691fa66cd22d631646c554251bdd..7328ab4bcb11b09713fed0625a0988cb1c9d43f2 100644 +--- a/net/minecraft/world/level/block/BedBlock.java ++++ b/net/minecraft/world/level/block/BedBlock.java +@@ -346,7 +346,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + BlockPos blockPos = pos.relative(state.getValue(FACING)); + level.setBlock(blockPos, state.setValue(PART, BedPart.HEAD), 3); + // CraftBukkit start - SPIGOT-7315: Don't updated if we capture block states +- if (level.captureBlockStates) { ++ if (level.getCurrentWorldData().captureBlockStates) { // Folia - region threading + return; + } + // CraftBukkit end +diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java +index 976de81d65b6494cdad20f4ec5125fceec86f951..aa09b2e8fac82ab954f581df3d41153c6244c2e8 100644 +--- a/net/minecraft/world/level/block/Block.java ++++ b/net/minecraft/world/level/block/Block.java +@@ -362,8 +362,8 @@ public class Block extends BlockBehaviour implements ItemLike { + ItemEntity itemEntity = itemEntitySupplier.get(); + itemEntity.setDefaultPickUpDelay(); + // CraftBukkit start +- if (level.captureDrops != null) { +- level.captureDrops.add(itemEntity); ++ if (level.getCurrentWorldData().captureDrops != null) { // Folia - region threading ++ level.getCurrentWorldData().captureDrops.add(itemEntity); // Folia - region threading + } else { + level.addFreshEntity(itemEntity); + } +diff --git a/net/minecraft/world/level/block/BushBlock.java b/net/minecraft/world/level/block/BushBlock.java +index bc52568bfa56635300266424488e524d77d95e09..068e65fb7efd52b36ba7f49829da80d82753e78e 100644 +--- a/net/minecraft/world/level/block/BushBlock.java ++++ b/net/minecraft/world/level/block/BushBlock.java +@@ -38,7 +38,7 @@ public abstract class BushBlock extends Block { + // CraftBukkit start + if (!state.canSurvive(level, pos)) { + // Suppress during worldgen +- if (!(level instanceof net.minecraft.server.level.ServerLevel serverLevel && serverLevel.hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(serverLevel, pos).isCancelled()) { // Paper ++ if (!(level instanceof net.minecraft.server.level.ServerLevel serverLevel && serverLevel.getCurrentWorldData().hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(serverLevel, pos).isCancelled()) { // Paper // Folia - region threading + return Blocks.AIR.defaultBlockState(); + } + } +diff --git a/net/minecraft/world/level/block/DaylightDetectorBlock.java b/net/minecraft/world/level/block/DaylightDetectorBlock.java +index a83d1dd4cac85d34f695333fd917a41f14dd5715..17532ef2cc5e21e68a1d51146641ae124a67f79e 100644 +--- a/net/minecraft/world/level/block/DaylightDetectorBlock.java ++++ b/net/minecraft/world/level/block/DaylightDetectorBlock.java +@@ -110,7 +110,7 @@ public class DaylightDetectorBlock extends BaseEntityBlock { + } + + private static void tickEntity(Level level, BlockPos pos, BlockState state, DaylightDetectorBlockEntity blockEntity) { +- if (level.getGameTime() % 20L == 0L) { ++ if (level.getRedstoneGameTime() % 20L == 0L) { // Folia - region threading + updateSignalStrength(state, level, pos); + } + } +diff --git a/net/minecraft/world/level/block/DispenserBlock.java b/net/minecraft/world/level/block/DispenserBlock.java +index e0a4d41e5bcf144ea4c10d6f633c3a95ed2c5aec..0ff736a45776bbf16f32ac05f099bb656aa3b9a6 100644 +--- a/net/minecraft/world/level/block/DispenserBlock.java ++++ b/net/minecraft/world/level/block/DispenserBlock.java +@@ -50,7 +50,7 @@ public class DispenserBlock extends BaseEntityBlock { + private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior(); + public static final Map DISPENSER_REGISTRY = new IdentityHashMap<>(); + private static final int TRIGGER_DURATION = 4; +- public static boolean eventFired = false; // CraftBukkit ++ public static ThreadLocal eventFired = ThreadLocal.withInitial(() -> Boolean.FALSE); // CraftBukkit // Folia - region threading + + @Override + public MapCodec codec() { +@@ -96,7 +96,7 @@ public class DispenserBlock extends BaseEntityBlock { + DispenseItemBehavior dispenseMethod = this.getDispenseMethod(level, item); + if (dispenseMethod != DispenseItemBehavior.NOOP) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent +- DispenserBlock.eventFired = false; // CraftBukkit - reset event status ++ DispenserBlock.eventFired.set(Boolean.FALSE); // CraftBukkit - reset event status // Folia - region threading + dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item)); + } + } +diff --git a/net/minecraft/world/level/block/DoublePlantBlock.java b/net/minecraft/world/level/block/DoublePlantBlock.java +index 7d033444ab5f89fae3c571a67ede6e7eff378945..e46c4071a955d880d61235d0861d8752ab3b860e 100644 +--- a/net/minecraft/world/level/block/DoublePlantBlock.java ++++ b/net/minecraft/world/level/block/DoublePlantBlock.java +@@ -118,7 +118,7 @@ public class DoublePlantBlock extends BushBlock { + + protected static void preventDropFromBottomPart(Level level, BlockPos pos, BlockState state, Player player) { + // CraftBukkit start +- if (((net.minecraft.server.level.ServerLevel)level).hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(level, pos).isCancelled()) { // Paper ++ if (((net.minecraft.server.level.ServerLevel)level).getCurrentWorldData().hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(level, pos).isCancelled()) { // Paper // Folia - region threading + return; + } + // CraftBukkit end +diff --git a/net/minecraft/world/level/block/EndGatewayBlock.java b/net/minecraft/world/level/block/EndGatewayBlock.java +index 84a1bd5e40e635962d795506861447851e443eee..a7b8e2b702fbe512c9633075515da6a430e76861 100644 +--- a/net/minecraft/world/level/block/EndGatewayBlock.java ++++ b/net/minecraft/world/level/block/EndGatewayBlock.java +@@ -111,17 +111,43 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { + if (portalPosition == null) { + return null; + } else { +- return entity instanceof ThrownEnderpearl +- ? new TeleportTransition(level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY) // CraftBukkit +- : new TeleportTransition( +- level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY // CraftBukkit +- ); ++ return getTeleportTransition(level, entity, portalPosition); // Folia - region threading + } + } else { + return null; + } + } + ++ // Folia start - region threading ++ public static TeleportTransition getTeleportTransition(ServerLevel level, Entity entity, Vec3 portalPosition) { ++ return entity instanceof ThrownEnderpearl ++ ? new TeleportTransition(level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY) // CraftBukkit ++ : new TeleportTransition( ++ level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY // CraftBukkit ++ ); ++ } ++ ++ @Override ++ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget, BlockPos portalPos) { ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(portalTarget)) { ++ return false; ++ } ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(sourceWorld, portalPos)) { ++ return false; ++ } ++ ++ BlockEntity tile = sourceWorld.getBlockEntity(portalPos); ++ ++ if (!(tile instanceof TheEndGatewayBlockEntity endGateway)) { ++ return false; ++ } ++ ++ return TheEndGatewayBlockEntity.teleportRegionThreading( ++ sourceWorld, portalPos, portalTarget, endGateway, TeleportTransition.PLACE_PORTAL_TICKET ++ ); ++ } ++ // Folia end - region threading ++ + @Override + protected RenderShape getRenderShape(BlockState state) { + return RenderShape.INVISIBLE; +diff --git a/net/minecraft/world/level/block/EndPortalBlock.java b/net/minecraft/world/level/block/EndPortalBlock.java +index 01cddd7001b4a7f99c1b1d147fac904d3064d733..177735cf744e564081e4c140a0f8210c3a07e037 100644 +--- a/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/net/minecraft/world/level/block/EndPortalBlock.java +@@ -63,7 +63,7 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { + level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) return; // Paper - make cancellable + // CraftBukkit end +- if (!level.isClientSide && level.dimension() == Level.END && entity instanceof ServerPlayer serverPlayer && !serverPlayer.seenCredits) { ++ if (false && !level.isClientSide && level.dimension() == Level.END && entity instanceof ServerPlayer serverPlayer && !serverPlayer.seenCredits) { // Folia - region threading - do not show credits + if (level.paperConfig().misc.disableEndCredits) {serverPlayer.seenCredits = true; return;} // Paper - Option to disable end credits + serverPlayer.showEndCredits(); + } else { +@@ -113,6 +113,20 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { + } + } + ++ // Folia start - region threading ++ @Override ++ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget, BlockPos portalPos) { ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(portalTarget)) { ++ return false; ++ } ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(sourceWorld, portalPos)) { ++ return false; ++ } ++ ++ return portalTarget.endPortalLogicAsync(portalPos); ++ } ++ // Folia end - region threading ++ + @Override + public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { + double d = pos.getX() + random.nextDouble(); +diff --git a/net/minecraft/world/level/block/FarmBlock.java b/net/minecraft/world/level/block/FarmBlock.java +index 47c9b32c89e7e6f84a279c2f6098ada77dc58b6b..1d97daccd595df427104aadf37eaa2861e6cb6e1 100644 +--- a/net/minecraft/world/level/block/FarmBlock.java ++++ b/net/minecraft/world/level/block/FarmBlock.java +@@ -95,8 +95,8 @@ public class FarmBlock extends Block { + @Override + protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { + int moistureValue = state.getValue(MOISTURE); +- if (moistureValue > 0 && level.paperConfig().tickRates.wetFarmland != 1 && (level.paperConfig().tickRates.wetFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.wetFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks +- if (moistureValue == 0 && level.paperConfig().tickRates.dryFarmland != 1 && (level.paperConfig().tickRates.dryFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.dryFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks ++ if (moistureValue > 0 && level.paperConfig().tickRates.wetFarmland != 1 && (level.paperConfig().tickRates.wetFarmland < 1 || (level.getRedstoneGameTime() + pos.hashCode()) % level.paperConfig().tickRates.wetFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks // Folia - region threading ++ if (moistureValue == 0 && level.paperConfig().tickRates.dryFarmland != 1 && (level.paperConfig().tickRates.dryFarmland < 1 || (level.getRedstoneGameTime() + pos.hashCode()) % level.paperConfig().tickRates.dryFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks // Folia - region threading + if (!isNearWater(level, pos) && !level.isRainingAt(pos.above())) { + if (moistureValue > 0) { + org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(level, pos, state.setValue(FarmBlock.MOISTURE, moistureValue - 1), 2); // CraftBukkit +diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java +index 85f0eac75784565c658c5178c544f969db3d6f54..81edac1fa383c6875c7a0439f2a160c11ef77a41 100644 +--- a/net/minecraft/world/level/block/FungusBlock.java ++++ b/net/minecraft/world/level/block/FungusBlock.java +@@ -76,9 +76,9 @@ public class FungusBlock extends BushBlock implements BonemealableBlock { + // CraftBukkit start + .map((value) -> { + if (this == Blocks.WARPED_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; ++ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.WARPED_FUNGUS); // Folia - region threading + } else if (this == Blocks.CRIMSON_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; ++ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.CRIMSON_FUNGUS); // Folia - region threading + } + return value; + }) +diff --git a/net/minecraft/world/level/block/HoneyBlock.java b/net/minecraft/world/level/block/HoneyBlock.java +index bab3ac2c4be08ea7589752b8472c1e13bcaab76a..776216db8097ceadc81d2f8401ea71447769b396 100644 +--- a/net/minecraft/world/level/block/HoneyBlock.java ++++ b/net/minecraft/world/level/block/HoneyBlock.java +@@ -94,7 +94,7 @@ public class HoneyBlock extends HalfTransparentBlock { + } + + private void maybeDoSlideAchievement(Entity entity, BlockPos pos) { +- if (entity instanceof ServerPlayer && entity.level().getGameTime() % 20L == 0L) { ++ if (entity instanceof ServerPlayer && entity.level().getRedstoneGameTime() % 20L == 0L) { // Folia - region threading + CriteriaTriggers.HONEY_BLOCK_SLIDE.trigger((ServerPlayer)entity, entity.level().getBlockState(pos)); + } + } +diff --git a/net/minecraft/world/level/block/LightningRodBlock.java b/net/minecraft/world/level/block/LightningRodBlock.java +index 534de49aec290766d6bc2523bb3975df775b5881..d79b3d328915096d723c0e3e6b6eb75cfe5bac51 100644 +--- a/net/minecraft/world/level/block/LightningRodBlock.java ++++ b/net/minecraft/world/level/block/LightningRodBlock.java +@@ -116,7 +116,7 @@ public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBloc + @Override + public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { + if (level.isThundering() +- && level.random.nextInt(200) <= level.getGameTime() % 200L ++ && level.random.nextInt(200) <= level.getRedstoneGameTime() % 200L // Folia - region threading + && pos.getY() == level.getHeight(Heightmap.Types.WORLD_SURFACE, pos.getX(), pos.getZ()) - 1) { + ParticleUtils.spawnParticlesAlongAxis(state.getValue(FACING).getAxis(), level, pos, 0.125, ParticleTypes.ELECTRIC_SPARK, UniformInt.of(1, 2)); + } +diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java +index 904369f4d7db41026183f2de7c96c2f0f4dc204d..223b1789ba94f763e29fb5e74aade787681e9f5b 100644 +--- a/net/minecraft/world/level/block/MushroomBlock.java ++++ b/net/minecraft/world/level/block/MushroomBlock.java +@@ -94,7 +94,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + return false; + } else { + level.removeBlock(pos, false); +- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit ++ SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // Folia - region threading + if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { + return true; + } else { +diff --git a/net/minecraft/world/level/block/NetherPortalBlock.java b/net/minecraft/world/level/block/NetherPortalBlock.java +index e2eb693b0130513115392cb0cb5a829ede5be8c5..68e1b1737c8b7af39f22dd4d28b879b5c3d52f65 100644 +--- a/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -181,6 +181,33 @@ public class NetherPortalBlock extends Block implements Portal { + } + } + ++ // Folia start - region threading ++ @Override ++ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget, BlockPos portalPos) { ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(portalTarget)) { ++ return false; ++ } ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(sourceWorld, portalPos)) { ++ return false; ++ } ++ ++ return portalTarget.netherPortalLogicAsync(portalPos); ++ } ++ ++ public static BlockUtil.FoundRectangle findPortalAround(ServerLevel world, BlockPos rough, WorldBorder worldBorder, int searchRadius) { ++ BlockPos found = world.getPortalForcer().findClosestPortalPosition(rough, worldBorder, searchRadius).orElse(null); ++ if (found == null) { ++ return null; ++ } ++ ++ BlockState portalState = world.getBlockStateFromEmptyChunk(found); ++ ++ return BlockUtil.getLargestRectangleAround(found, portalState.getValue(BlockStateProperties.HORIZONTAL_AXIS), 21, Direction.Axis.Y, 21, (pos) -> { ++ return world.getBlockStateFromEmptyChunk(pos) == portalState; ++ }); ++ } ++ // Folia end - region threading ++ + @Nullable + private TeleportTransition getExitPortal(ServerLevel level, Entity entity, BlockPos pos, BlockPos exitPos, boolean isNether, WorldBorder worldBorder, int searchRadius, boolean canCreatePortal, int createRadius) { // CraftBukkit + Optional optional = level.getPortalForcer().findClosestPortalPosition(exitPos, worldBorder, searchRadius); // CraftBukkit +@@ -188,14 +215,14 @@ public class NetherPortalBlock extends Block implements Portal { + TeleportTransition.PostTeleportTransition postTeleportTransition; + if (optional.isPresent()) { + BlockPos blockPos = optional.get(); +- BlockState blockState = level.getBlockState(blockPos); ++ BlockState blockState = level.getBlockStateFromEmptyChunk(blockPos); // Folia - region threading + largestRectangleAround = BlockUtil.getLargestRectangleAround( + blockPos, + blockState.getValue(BlockStateProperties.HORIZONTAL_AXIS), + 21, + Direction.Axis.Y, + 21, +- blockPos1 -> level.getBlockState(blockPos1) == blockState ++ blockPos1 -> level.getBlockStateFromEmptyChunk(blockPos1) == blockState // Folia - region threading + ); + postTeleportTransition = TeleportTransition.PLAY_PORTAL_SOUND.then(entity1 -> entity1.placePortalTicket(blockPos)); + } else if (canCreatePortal) { // CraftBukkit +@@ -238,7 +265,7 @@ public class NetherPortalBlock extends Block implements Portal { + return createDimensionTransition(level, rectangle, axis, relativePortalPosition, entity, postTeleportTransition); + } + +- private static TeleportTransition createDimensionTransition( ++ public static TeleportTransition createDimensionTransition( // Folia - region threading - public + ServerLevel level, + BlockUtil.FoundRectangle rectangle, + Direction.Axis axis, +diff --git a/net/minecraft/world/level/block/Portal.java b/net/minecraft/world/level/block/Portal.java +index c941b0e05d98fa59669757174887955e6319eddb..3883a437d99e5d8b13c55764613d630e29e75bc4 100644 +--- a/net/minecraft/world/level/block/Portal.java ++++ b/net/minecraft/world/level/block/Portal.java +@@ -14,6 +14,10 @@ public interface Portal { + @Nullable + TeleportTransition getPortalDestination(ServerLevel level, Entity entity, BlockPos pos); + ++ // Folia start - region threading ++ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget, BlockPos portalPos); ++ // Folia end - region threading ++ + default Portal.Transition getLocalTransition() { + return Portal.Transition.NONE; + } +diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java +index 12c9d60314c99fb65e640d255a2d0c6b7790ad4d..6ba86c5e55d09fd99e81e40db4614ef14246bdc3 100644 +--- a/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -91,7 +91,7 @@ public class RedStoneWireBlock extends Block { + private static final float PARTICLE_DENSITY = 0.2F; + private final BlockState crossState; + private final RedstoneWireEvaluator evaluator = new DefaultRedstoneWireEvaluator(this); +- public boolean shouldSignal = true; ++ //public boolean shouldSignal = true; // Folia - region threading - move to regionised world data + + @Override + public MapCodec codec() { +@@ -293,6 +293,11 @@ public class RedStoneWireBlock extends Block { + // Paper start - Optimize redstone (Eigencraft) + // The bulk of the new functionality is found in RedstoneWireTurbo.java + io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this); ++ // Folia start - region threading ++ private io.papermc.paper.redstone.RedstoneWireTurbo getTurbo(Level world) { ++ return world.getCurrentWorldData().turbo; ++ } ++ // Folia end - region threading + + /* + * Modified version of pre-existing updateSurroundingRedstone, which is called from +@@ -308,7 +313,7 @@ public class RedStoneWireBlock extends Block { + if (orientation != null) { + source = pos.relative(orientation.getFront().getOpposite()); + } +- turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ getTurbo(worldIn).updateSurroundingRedstone(worldIn, pos, state, source); // Folia - region threading + return; + } + updatePowerStrength(worldIn, pos, state, orientation, blockAdded); +@@ -336,7 +341,7 @@ public class RedStoneWireBlock extends Block { + // [Space Walker] suppress shape updates and emit those manually to + // bypass the new neighbor update stack. + if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) { +- turbo.updateNeighborShapes(level, pos, state); ++ this.getTurbo(level).updateNeighborShapes(level, pos, state); // Folia - region threading + } + } + } +@@ -353,9 +358,9 @@ public class RedStoneWireBlock extends Block { + } + + public int getBlockSignal(Level level, BlockPos pos) { +- this.shouldSignal = false; ++ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal = false; // Folia - region threading + int bestNeighborSignal = level.getBestNeighborSignal(pos); +- this.shouldSignal = true; ++ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal = true; // Folia - region threading + return bestNeighborSignal; + } + +@@ -450,12 +455,12 @@ public class RedStoneWireBlock extends Block { + + @Override + protected int getDirectSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) { +- return !this.shouldSignal ? 0 : blockState.getSignal(blockAccess, pos, side); ++ return !io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal ? 0 : blockState.getSignal(blockAccess, pos, side); // Folia - region threading + } + + @Override + protected int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) { +- if (this.shouldSignal && side != Direction.DOWN) { ++ if (io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal && side != Direction.DOWN) { // Folia - region threading + int powerValue = blockState.getValue(POWER); + if (powerValue == 0) { + return 0; +@@ -487,7 +492,10 @@ public class RedStoneWireBlock extends Block { + + @Override + protected boolean isSignalSource(BlockState state) { +- return this.shouldSignal; ++ // Folia start - region threading ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); ++ return worldData == null || worldData.shouldSignal; ++ // Folia end - region threading + } + + public static int getColorForPower(int power) { +diff --git a/net/minecraft/world/level/block/RedstoneTorchBlock.java b/net/minecraft/world/level/block/RedstoneTorchBlock.java +index 18420ec1f5776b018010f26e59aba00ae5bd0723..d5ac5d8fddeaff0def61a909faf2c909337ada57 100644 +--- a/net/minecraft/world/level/block/RedstoneTorchBlock.java ++++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java +@@ -73,10 +73,10 @@ public class RedstoneTorchBlock extends BaseTorchBlock { + protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { + boolean hasNeighborSignal = this.hasNeighborSignal(level, pos, state); + // Paper start - Faster redstone torch rapid clock removal +- java.util.ArrayDeque redstoneUpdateInfos = level.redstoneUpdateInfos; ++ java.util.ArrayDeque redstoneUpdateInfos = level.getCurrentWorldData().redstoneUpdateInfos; // Folia - region threading + if (redstoneUpdateInfos != null) { + RedstoneTorchBlock.Toggle curr; +- while ((curr = redstoneUpdateInfos.peek()) != null && level.getGameTime() - curr.when > 60L) { ++ while ((curr = redstoneUpdateInfos.peek()) != null && level.getRedstoneGameTime() - curr.when > 60L) { // Folia - region threading + redstoneUpdateInfos.poll(); + } + } +@@ -154,13 +154,13 @@ public class RedstoneTorchBlock extends BaseTorchBlock { + + private static boolean isToggledTooFrequently(Level level, BlockPos pos, boolean logToggle) { + // Paper start - Faster redstone torch rapid clock removal +- java.util.ArrayDeque list = level.redstoneUpdateInfos; ++ java.util.ArrayDeque list = level.getCurrentWorldData().redstoneUpdateInfos; // Folia - region threading + if (list == null) { +- list = level.redstoneUpdateInfos = new java.util.ArrayDeque<>(); ++ list = level.getCurrentWorldData().redstoneUpdateInfos = new java.util.ArrayDeque<>(); // Folia - region threading + } + // Paper end - Faster redstone torch rapid clock removal + if (logToggle) { +- list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), level.getGameTime())); ++ list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), level.getRedstoneGameTime())); // Folia - region threading + } + + int i = 0; +@@ -182,12 +182,18 @@ public class RedstoneTorchBlock extends BaseTorchBlock { + } + + public static class Toggle { +- final BlockPos pos; +- final long when; ++ public final BlockPos pos; // Folia - region threading ++ long when; // Folia - region threading + + public Toggle(BlockPos pos, long when) { + this.pos = pos; + this.when = when; + } ++ ++ // Folia start - region ticking ++ public void offsetTime(long offset) { ++ this.when += offset; ++ } ++ // Folia end - region ticking + } + } +diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java +index e014f052e9b0f5ca6b28044e2389782b7d0e0cb8..cc9e253d3033d3e970891067329aa281e85464f7 100644 +--- a/net/minecraft/world/level/block/SaplingBlock.java ++++ b/net/minecraft/world/level/block/SaplingBlock.java +@@ -26,7 +26,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + protected static final float AABB_OFFSET = 6.0F; + protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 12.0, 14.0); + protected final TreeGrower treeGrower; +- public static org.bukkit.TreeType treeType; // CraftBukkit ++ public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // CraftBukkit // Folia - region threading + + @Override + public MapCodec codec() { +@@ -56,18 +56,19 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + level.setBlock(pos, state.cycle(STAGE), 4); + } else { + // CraftBukkit start +- if (level.captureTreeGeneration) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading ++ if (worldData.captureTreeGeneration) { // Folia - region threading + this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); + } else { +- level.captureTreeGeneration = true; ++ worldData.captureTreeGeneration = true; // Folia - region threading + this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); +- level.captureTreeGeneration = false; +- if (!level.capturedBlockStates.isEmpty()) { +- org.bukkit.TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; ++ worldData.captureTreeGeneration = false; // Folia - region threading ++ if (!worldData.capturedBlockStates.isEmpty()) { // Folia - region threading ++ org.bukkit.TreeType treeType = SaplingBlock.treeTypeRT.get(); // Folia - region threading ++ SaplingBlock.treeTypeRT.set(null); // Folia - region threading + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld()); +- java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); +- level.capturedBlockStates.clear(); ++ java.util.List blocks = new java.util.ArrayList<>(worldData.capturedBlockStates.values()); // Folia - region threading ++ worldData.capturedBlockStates.clear(); // Folia - region threading + org.bukkit.event.world.StructureGrowEvent event = null; + if (treeType != null) { + event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks); +diff --git a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java +index 722f2b9a24679e0fc67aae2cd27051f96f962efe..fb8c09b18ea4112cbbe6e93bf6b9804d79628d36 100644 +--- a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java ++++ b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java +@@ -50,7 +50,7 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock { + + @Override + protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { +- if (this instanceof GrassBlock && level.paperConfig().tickRates.grassSpread != 1 && (level.paperConfig().tickRates.grassSpread < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks ++ if (this instanceof GrassBlock && level.paperConfig().tickRates.grassSpread != 1 && (level.paperConfig().tickRates.grassSpread < 1 || (io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + pos.hashCode()) % level.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks // Folia - regionised ticking + // Paper start - Perf: optimize dirt and snow spreading + final net.minecraft.world.level.chunk.ChunkAccess cachedBlockChunk = level.getChunkIfLoaded(pos); + if (cachedBlockChunk == null) { // Is this needed? +diff --git a/net/minecraft/world/level/block/WitherSkullBlock.java b/net/minecraft/world/level/block/WitherSkullBlock.java +index dc70aaa8d929c40c5f34c8facc1ad2bff4e98768..3ea53116725798a1eedb4802d6ebd7a32d8cccfd 100644 +--- a/net/minecraft/world/level/block/WitherSkullBlock.java ++++ b/net/minecraft/world/level/block/WitherSkullBlock.java +@@ -51,7 +51,7 @@ public class WitherSkullBlock extends SkullBlock { + } + + public static void checkSpawn(Level level, BlockPos pos, SkullBlockEntity blockEntity) { +- if (level.captureBlockStates) return; // CraftBukkit ++ if (level.getCurrentWorldData().captureBlockStates) return; // CraftBukkit // Folia - region threading + if (!level.isClientSide) { + BlockState blockState = blockEntity.getBlockState(); + boolean flag = blockState.is(Blocks.WITHER_SKELETON_SKULL) || blockState.is(Blocks.WITHER_SKELETON_WALL_SKULL); +diff --git a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index deef33d96db188cb297f04b581ab29e77e3716a9..413288e4a654b5ff8cc009b401d602731f63ec6d 100644 +--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -211,7 +211,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + } + + int i = blockEntity.levels; final int originalLevels = i; // Paper - OBFHELPER +- if (level.getGameTime() % 80L == 0L) { ++ if (level.getRedstoneGameTime() % 80L == 0L) { // Folia - region threading + if (!blockEntity.beamSections.isEmpty()) { + blockEntity.levels = updateBase(level, x, y, z); + } +@@ -345,7 +345,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name + list = level.getEntitiesOfClass(Player.class, aabb); // Diff from applyEffect + } else { + list = new java.util.ArrayList<>(); +- for (final Player player : level.players()) { ++ for (final Player player : level.getLocalPlayers()) { // Folia - region threading + if (!net.minecraft.world.entity.EntitySelector.NO_SPECTATORS.test(player)) continue; + if (player.getBoundingBox().intersects(aabb)) { + list.add(player); +diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java +index 77618757c0e678532dbab814aceed83f7f1cd892..003e9db957023486278679803b313ce89d573587 100644 +--- a/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -26,7 +26,7 @@ import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; + + public abstract class BlockEntity { +- static boolean ignoreBlockEntityUpdates; // Paper - Perf: Optimize Hoppers ++ static final ThreadLocal IGNORE_TILE_UPDATES = ThreadLocal.withInitial(() -> Boolean.FALSE); // Paper - Perf: Optimize Hoppers // Folia - region threading + // CraftBukkit start - data containers + private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); + public org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer; +@@ -40,6 +40,12 @@ public abstract class BlockEntity { + private BlockState blockState; + private DataComponentMap components = DataComponentMap.EMPTY; + ++ // Folia start - region ticking ++ public void updateTicks(final long fromTickOffset, final long fromRedstoneTimeOffset) { ++ ++ } ++ // Folia end - region ticking ++ + public BlockEntity(BlockEntityType type, BlockPos pos, BlockState blockState) { + this.type = type; + this.worldPosition = pos.immutable(); +@@ -197,7 +203,7 @@ public abstract class BlockEntity { + + public void setChanged() { + if (this.level != null) { +- if (ignoreBlockEntityUpdates) return; // Paper - Perf: Optimize Hoppers ++ if (IGNORE_TILE_UPDATES.get().booleanValue()) return; // Paper - Perf: Optimize Hoppers // Folia - region threading + setChanged(this.level, this.worldPosition, this.blockState); + } + } +diff --git a/net/minecraft/world/level/block/entity/CommandBlockEntity.java b/net/minecraft/world/level/block/entity/CommandBlockEntity.java +index de75569d44855d9d6ec28cfee4403ecb6b45c4d3..c1e3a99a5fe917c728763be16c9a92d7252739a3 100644 +--- a/net/minecraft/world/level/block/entity/CommandBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/CommandBlockEntity.java +@@ -66,6 +66,13 @@ public class CommandBlockEntity extends BlockEntity { + ); + } + ++ // Folia start ++ @Override ++ public void threadCheck() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) CommandBlockEntity.this.level, CommandBlockEntity.this.worldPosition, "Asynchronous sendSystemMessage to a command block"); ++ } ++ // Folia end ++ + @Override + public boolean isValid() { + return !CommandBlockEntity.this.isRemoved(); +diff --git a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +index 9d80625fc95e4968cf80492dc7ecf1fd27e585b8..2938c1d35d5b19cfe49e12607f1e1d9342114f2c 100644 +--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +@@ -81,7 +81,7 @@ public class ConduitBlockEntity extends BlockEntity { + + public static void clientTick(Level level, BlockPos pos, BlockState state, ConduitBlockEntity blockEntity) { + blockEntity.tickCount++; +- long gameTime = level.getGameTime(); ++ long gameTime = level.getRedstoneGameTime(); // Folia - region threading + List list = blockEntity.effectBlocks; + if (gameTime % 40L == 0L) { + blockEntity.isActive = updateShape(level, pos, list); +@@ -97,7 +97,7 @@ public class ConduitBlockEntity extends BlockEntity { + + public static void serverTick(Level level, BlockPos pos, BlockState state, ConduitBlockEntity blockEntity) { + blockEntity.tickCount++; +- long gameTime = level.getGameTime(); ++ long gameTime = level.getRedstoneGameTime(); // Folia - region threading + List list = blockEntity.effectBlocks; + if (gameTime % 40L == 0L) { + boolean flag = updateShape(level, pos, list); +diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index 5cd1326ad5d046c88b2b3449d610a78fa880b4cd..ae988c4910421fb720177178ef6136e595ae6946 100644 +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -34,7 +34,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + private static final int[][] CACHED_SLOTS = new int[54][]; + private NonNullList items = NonNullList.withSize(5, ItemStack.EMPTY); + public int cooldownTime = -1; +- private long tickedGameTime; ++ private long tickedGameTime = Long.MIN_VALUE; // Folia - region threading + private Direction facing; + + // CraftBukkit start - add fields and methods +@@ -67,6 +67,15 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + // CraftBukkit end + ++ // Folia start - region threading ++ @Override ++ public void updateTicks(final long fromTickOffset, final long fromRedstoneTimeOffset) { ++ super.updateTicks(fromTickOffset, fromRedstoneTimeOffset); ++ if (this.tickedGameTime != Long.MIN_VALUE) { ++ this.tickedGameTime += fromRedstoneTimeOffset; ++ } ++ } ++ // Folia end - region threading + + public HopperBlockEntity(BlockPos pos, BlockState blockState) { + super(BlockEntityType.HOPPER, pos, blockState); +@@ -125,7 +134,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + public static void pushItemsTick(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity) { + blockEntity.cooldownTime--; +- blockEntity.tickedGameTime = level.getGameTime(); ++ blockEntity.tickedGameTime = level.getRedstoneGameTime(); // Folia - region threading + if (!blockEntity.isOnCooldown()) { + blockEntity.setCooldown(0); + // Spigot start +@@ -213,12 +222,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + // Paper start - Perf: Optimize Hoppers +- public static boolean skipHopperEvents; +- private static boolean skipPullModeEventFire; +- private static boolean skipPushModeEventFire; ++ // Folia - region threading - moved to RegionizedWorldData + + private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { +- skipPushModeEventFire = skipHopperEvents; ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading ++ worldData.skipPushModeEventFire = worldData.skipHopperEvents; // Folia - region threading + boolean foundItem = false; + for (int i = 0; i < hopper.getContainerSize(); ++i) { + final ItemStack item = hopper.getItem(i); +@@ -233,7 +241,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + // We only need to fire the event once to give protection plugins a chance to cancel this event + // Because nothing uses getItem, every event call should end up the same result. +- if (!skipPushModeEventFire) { ++ if (!worldData.skipPushModeEventFire) { // Folia - region threading + movedItem = callPushMoveEvent(destination, movedItem, hopper); + if (movedItem == null) { // cancelled + origItemStack.setCount(originalItemCount); +@@ -263,13 +271,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading + ItemStack movedItem = origItemStack; + final int originalItemCount = origItemStack.getCount(); + final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); + container.setChanged(); // original logic always marks source inv as changed even if no move happens. + movedItem.setCount(movedItemCount); + +- if (!skipPullModeEventFire) { ++ if (!worldData.skipPullModeEventFire) { // Folia - region threading + movedItem = callPullMoveEvent(hopper, container, movedItem); + if (movedItem == null) { // cancelled + origItemStack.setCount(originalItemCount); +@@ -289,9 +298,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); + } + +- ignoreBlockEntityUpdates = true; ++ IGNORE_TILE_UPDATES.set(true); // Folia - region threading + container.setItem(i, origItemStack); +- ignoreBlockEntityUpdates = false; ++ IGNORE_TILE_UPDATES.set(false); // Folia - region threading + container.setChanged(); + return true; + } +@@ -306,6 +315,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + private static ItemStack callPushMoveEvent(Container destination, ItemStack itemStack, HopperBlockEntity hopper) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading + final org.bukkit.inventory.Inventory destinationInventory = getInventory(destination); + final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent( + hopper.getOwner(false).getInventory(), +@@ -315,7 +325,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + ); + final boolean result = event.callEvent(); + if (!event.calledGetItem && !event.calledSetItem) { +- skipPushModeEventFire = true; ++ worldData.skipPushModeEventFire = true; // Folia - region threading + } + if (!result) { + applyCooldown(hopper); +@@ -331,6 +341,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading + final org.bukkit.inventory.Inventory sourceInventory = getInventory(container); + final org.bukkit.inventory.Inventory destination = getInventory(hopper); + +@@ -338,7 +349,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), destination, false); + final boolean result = event.callEvent(); + if (!event.calledGetItem && !event.calledSetItem) { +- skipPullModeEventFire = true; ++ worldData.skipPullModeEventFire = true; // Folia - region threading + } + if (!result) { + applyCooldown(hopper); +@@ -524,12 +535,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + public static boolean suckInItems(Level level, Hopper hopper) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading + BlockPos blockPos = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ()); + BlockState blockState = level.getBlockState(blockPos); + Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState); + if (sourceContainer != null) { + Direction direction = Direction.DOWN; +- skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers ++ worldData.skipPullModeEventFire = worldData.skipHopperEvents; // Paper - Perf: Optimize Hoppers // Folia - region threading + + for (int i : getSlots(sourceContainer, direction)) { + if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot +@@ -678,9 +690,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + stack = stack.split(destination.getMaxStackSize()); + } + // Spigot end +- ignoreBlockEntityUpdates = true; // Paper - Perf: Optimize Hoppers ++ IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading + destination.setItem(slot, stack); +- ignoreBlockEntityUpdates = false; // Paper - Perf: Optimize Hoppers ++ IGNORE_TILE_UPDATES.set(Boolean.FALSE); // Paper - Perf: Optimize Hoppers // Folia - region threading + stack = leftover; // Paper - Make hoppers respect inventory max stack size + flag = true; + } else if (canMergeItems(item, stack)) { +diff --git a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +index 1638eccef431fb68775af624110f1968f0c6dabd..bd6693af6412fb08a28ca9a71d5c70d54f72c6e6 100644 +--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +@@ -43,9 +43,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi + // Paper end - Fix NPE in SculkBloomEvent world access + + public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) { +- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. ++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(sculkCatalyst.getBlockPos()); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // Folia - region threading + sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true); +- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(null); // CraftBukkit // Folia - region threading + } + + @Override +diff --git a/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +index 5bf39c542757bf97da8909b65c22786a8a30385a..61887e6b052bca715c90dff5d9cd657e0b3f6a78 100644 +--- a/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +@@ -35,9 +35,12 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { + public long age; + private int teleportCooldown; + @Nullable +- public BlockPos exitPortal; ++ public volatile BlockPos exitPortal; // Folia - region threading - volatile + public boolean exactTeleport; + ++ private static final java.util.concurrent.atomic.AtomicLong SEARCHING_FOR_EXIT_ID_GENERATOR = new java.util.concurrent.atomic.AtomicLong(); // Folia - region threading ++ private Long searchingForExitId; // Folia - region threading ++ + public TheEndGatewayBlockEntity(BlockPos pos, BlockState blockState) { + super(BlockEntityType.END_GATEWAY, pos, blockState); + } +@@ -129,6 +132,104 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { + } + } + ++ // Folia start - region threading ++ private void trySearchForExit(ServerLevel world, BlockPos fromPos) { ++ if (this.searchingForExitId != null) { ++ return; ++ } ++ this.searchingForExitId = Long.valueOf(SEARCHING_FOR_EXIT_ID_GENERATOR.getAndIncrement()); ++ int chunkX = fromPos.getX() >> 4; ++ int chunkZ = fromPos.getZ() >> 4; ++ world.moonrise$getChunkTaskScheduler().chunkHolderManager.addTicketAtLevel( ++ net.minecraft.server.level.TicketType.END_GATEWAY_EXIT_SEARCH, ++ chunkX, chunkZ, ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.BLOCK_TICKING_TICKET_LEVEL, ++ this.searchingForExitId ++ ); ++ ++ ca.spottedleaf.concurrentutil.completable.CallbackCompletable complete = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); ++ ++ complete.addWaiter((tpLoc, throwable) -> { ++ // create the exit portal ++ TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", tpLoc); ++ TheEndGatewayBlockEntity.spawnGatewayPortal(world, tpLoc, EndGatewayConfiguration.knownExit(fromPos, false)); ++ ++ // need to go onto the tick thread to avoid saving issues ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( ++ world, chunkX, chunkZ, ++ () -> { ++ // update the exit portal location ++ TheEndGatewayBlockEntity.this.exitPortal = tpLoc; ++ ++ // remove ticket keeping the gateway loaded ++ world.moonrise$getChunkTaskScheduler().chunkHolderManager.removeTicketAtLevel( ++ net.minecraft.server.level.TicketType.END_GATEWAY_EXIT_SEARCH, ++ chunkX, chunkZ, ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.BLOCK_TICKING_TICKET_LEVEL, ++ this.searchingForExitId ++ ); ++ TheEndGatewayBlockEntity.this.searchingForExitId = null; ++ } ++ ); ++ }); ++ ++ findOrCreateValidTeleportPosRegionThreading(world, fromPos, complete); ++ } ++ ++ public static boolean teleportRegionThreading(ServerLevel portalWorld, BlockPos portalPos, ++ net.minecraft.world.entity.Entity toTeleport, ++ TheEndGatewayBlockEntity portalTile, ++ net.minecraft.world.level.portal.TeleportTransition.PostTeleportTransition post) { ++ // can we even teleport in this dimension? ++ if (portalTile.exitPortal == null && portalWorld.getTypeKey() != net.minecraft.world.level.dimension.LevelStem.END) { ++ return false; ++ } ++ ++ // First, find the position we are trying to teleport to ++ BlockPos teleportPos = portalTile.exitPortal; ++ boolean isExactTeleport = portalTile.exactTeleport; ++ ++ if (teleportPos == null) { ++ portalTile.trySearchForExit(portalWorld, portalPos); ++ return false; ++ } ++ ++ // note: we handle the position from the TeleportTransition ++ net.minecraft.world.level.portal.TeleportTransition teleport = net.minecraft.world.level.block.EndGatewayBlock.getTeleportTransition( ++ portalWorld, toTeleport, Vec3.atCenterOf(teleportPos) ++ ); ++ ++ ++ if (isExactTeleport) { ++ // blind teleport ++ return toTeleport.teleportAsync( ++ teleport, net.minecraft.world.entity.Entity.TELEPORT_FLAG_LOAD_CHUNK | net.minecraft.world.entity.Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS, ++ post == null ? null : (net.minecraft.world.entity.Entity teleportedEntity) -> { ++ post.onTransition(teleportedEntity); ++ } ++ ); ++ } else { ++ // we could hack around by first loading the chunks, then calling back to here and checking if the entity ++ // should be teleported, something something else... ++ // however, we know the target location cannot differ by one region section: so we can ++ // just teleport and adjust the position after ++ return toTeleport.teleportAsync( ++ teleport, net.minecraft.world.entity.Entity.TELEPORT_FLAG_LOAD_CHUNK | net.minecraft.world.entity.Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS, ++ (net.minecraft.world.entity.Entity teleportedEntity) -> { ++ // adjust to the final exit position ++ Vec3 adjusted = Vec3.atCenterOf(TheEndGatewayBlockEntity.findExitPosition(portalWorld, teleportPos)); ++ // teleportTo will adjust rider positions ++ teleportedEntity.teleportTo(adjusted.x, adjusted.y, adjusted.z); ++ ++ if (post != null) { ++ post.onTransition(teleportedEntity); ++ } ++ } ++ ); ++ } ++ } ++ // Folia end - region threading ++ + @Nullable + public Vec3 getPortalPosition(ServerLevel level, BlockPos pos) { + if (this.exitPortal == null && level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) { // CraftBukkit - work in alternate worlds +@@ -174,6 +275,124 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { + return findTallestBlock(level, blockPos, 16, true); + } + ++ // Folia start - region threading ++ private static void findOrCreateValidTeleportPosRegionThreading(ServerLevel world, BlockPos pos, ++ ca.spottedleaf.concurrentutil.completable.CallbackCompletable complete) { ++ ca.spottedleaf.concurrentutil.completable.CallbackCompletable tentativeSelection = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); ++ ++ tentativeSelection.addWaiter((vec3d, throwable) -> { ++ LevelChunk chunk = TheEndGatewayBlockEntity.getChunk(world, vec3d); ++ BlockPos blockposition1 = TheEndGatewayBlockEntity.findValidSpawnInChunk(chunk); ++ if (blockposition1 == null) { ++ BlockPos blockposition2 = BlockPos.containing(vec3d.x + 0.5D, 75.0D, vec3d.z + 0.5D); ++ ++ TheEndGatewayBlockEntity.LOGGER.debug("Failed to find a suitable block to teleport to, spawning an island on {}", blockposition2); ++ world.registryAccess().lookup(Registries.CONFIGURED_FEATURE).flatMap((iregistry) -> { ++ return iregistry.get(EndFeatures.END_ISLAND); ++ }).ifPresent((holder_c) -> { ++ ((net.minecraft.world.level.levelgen.feature.ConfiguredFeature) holder_c.value()).place(world, world.getChunkSource().getGenerator(), RandomSource.create(blockposition2.asLong()), blockposition2); ++ }); ++ blockposition1 = blockposition2; ++ } else { ++ TheEndGatewayBlockEntity.LOGGER.debug("Found suitable block to teleport to: {}", blockposition1); ++ } ++ ++ // Here, there is no guarantee the chunks in 1 radius are in this region due to the fact that we just chained ++ // possibly 16x chunk loads along an axis (findExitPortalXZPosTentativeRegionThreading) using the chunk queue ++ // (regioniser only guarantees at least 8 chunks along a single axis) ++ // so, we need to schedule for the next tick ++ int posX = blockposition1.getX(); ++ int posZ = blockposition1.getZ(); ++ int radius = 16; ++ ++ BlockPos finalBlockPosition1 = blockposition1; ++ world.moonrise$loadChunksAsync(blockposition1, radius, ++ ca.spottedleaf.concurrentutil.util.Priority.NORMAL, ++ (java.util.List chunks) -> { ++ // make sure chunks are kept loaded ++ for (net.minecraft.world.level.chunk.ChunkAccess access : chunks) { ++ world.chunkSource.addTicketAtLevel( ++ net.minecraft.server.level.TicketType.DELAYED, access.getPos(), ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.FULL_LOADED_TICKET_LEVEL, ++ net.minecraft.util.Unit.INSTANCE ++ ); ++ } ++ // now after the chunks are loaded, we can delay by one tick ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( ++ world, posX >> 4, posZ >> 4, () -> { ++ // find final location ++ BlockPos tpLoc = TheEndGatewayBlockEntity.findTallestBlock(world, finalBlockPosition1, radius, true).above(GATEWAY_HEIGHT_ABOVE_SURFACE); ++ ++ // done ++ complete.complete(tpLoc); ++ } ++ ); ++ } ++ ); ++ }); ++ ++ // fire off chain ++ findExitPortalXZPosTentativeRegionThreading(world, pos, tentativeSelection); ++ } ++ ++ private static void findExitPortalXZPosTentativeRegionThreading(ServerLevel world, BlockPos pos, ++ ca.spottedleaf.concurrentutil.completable.CallbackCompletable complete) { ++ Vec3 posDirFromOrigin = new Vec3(pos.getX(), 0.0D, pos.getZ()).normalize(); ++ Vec3 posDirExtruded = posDirFromOrigin.scale(1024.0D); ++ ++ class Vars { ++ int i = 16; ++ boolean mode = false; ++ Vec3 currPos = posDirExtruded; ++ } ++ Vars vars = new Vars(); ++ ++ Runnable handle = new Runnable() { ++ @Override ++ public void run() { ++ if (vars.mode != TheEndGatewayBlockEntity.isChunkEmpty(world, vars.currPos)) { ++ vars.i = 0; // fall back to completing ++ } ++ ++ // try to load next chunk ++ if (vars.i-- <= 0) { ++ if (vars.mode) { ++ complete.complete(vars.currPos); ++ return; ++ } ++ vars.mode = true; ++ vars.i = 16; ++ } ++ ++ vars.currPos = vars.currPos.add(posDirFromOrigin.scale(vars.mode ? 16.0 : -16.0)); ++ // schedule next iteration ++ world.moonrise$getChunkTaskScheduler().scheduleChunkLoad( ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(vars.currPos), ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(vars.currPos), ++ net.minecraft.world.level.chunk.status.ChunkStatus.FULL, ++ true, ++ ca.spottedleaf.concurrentutil.util.Priority.NORMAL, ++ (chunk) -> { ++ this.run(); ++ } ++ ); ++ } ++ }; ++ ++ // kick off first chunk load ++ world.moonrise$getChunkTaskScheduler().scheduleChunkLoad( ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(posDirExtruded), ++ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(posDirExtruded), ++ net.minecraft.world.level.chunk.status.ChunkStatus.FULL, ++ true, ++ ca.spottedleaf.concurrentutil.util.Priority.NORMAL, ++ (chunk) -> { ++ handle.run(); ++ } ++ ); ++ } ++ // Folia end - region threading ++ + private static Vec3 findExitPortalXZPosTentative(ServerLevel level, BlockPos pos) { + Vec3 vec3 = new Vec3(pos.getX(), 0.0, pos.getZ()).normalize(); + int i = 1024; +diff --git a/net/minecraft/world/level/block/entity/TickingBlockEntity.java b/net/minecraft/world/level/block/entity/TickingBlockEntity.java +index 28e3b73507b988f7234cbf29c4024c88180d0aef..c8facee29ee08e0975528083f89b64f0b593957f 100644 +--- a/net/minecraft/world/level/block/entity/TickingBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/TickingBlockEntity.java +@@ -10,4 +10,6 @@ public interface TickingBlockEntity { + BlockPos getPos(); + + String getType(); ++ ++ BlockEntity getTileEntity(); // Folia - region threading + } +diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java +index cf7311c507de09a8f89934e430b2201e8bdffe51..80de710b4e1528587b509e50bdd69983bcb608d0 100644 +--- a/net/minecraft/world/level/block/grower/TreeGrower.java ++++ b/net/minecraft/world/level/block/grower/TreeGrower.java +@@ -203,56 +203,58 @@ public final class TreeGrower { + + // CraftBukkit start + private void setTreeType(Holder> holder) { ++ org.bukkit.TreeType treeType; // Folia - region threading + ResourceKey> treeFeature = holder.unwrapKey().get(); + if (treeFeature == TreeFeatures.OAK || treeFeature == TreeFeatures.OAK_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; ++ treeType = org.bukkit.TreeType.TREE; // Folia - region threading + } else if (treeFeature == TreeFeatures.HUGE_RED_MUSHROOM) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM; ++ treeType = org.bukkit.TreeType.RED_MUSHROOM; // Folia - region threading + } else if (treeFeature == TreeFeatures.HUGE_BROWN_MUSHROOM) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM; ++ treeType = org.bukkit.TreeType.BROWN_MUSHROOM; // Folia - region threading + } else if (treeFeature == TreeFeatures.JUNGLE_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE; ++ treeType = org.bukkit.TreeType.COCOA_TREE; // Folia - region threading + } else if (treeFeature == TreeFeatures.JUNGLE_TREE_NO_VINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE; ++ treeType = org.bukkit.TreeType.SMALL_JUNGLE; // Folia - region threading + } else if (treeFeature == TreeFeatures.PINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD; ++ treeType = org.bukkit.TreeType.TALL_REDWOOD; // Folia - region threading + } else if (treeFeature == TreeFeatures.SPRUCE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD; ++ treeType = org.bukkit.TreeType.REDWOOD; // Folia - region threading + } else if (treeFeature == TreeFeatures.ACACIA) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA; ++ treeType = org.bukkit.TreeType.ACACIA; // Folia - region threading + } else if (treeFeature == TreeFeatures.BIRCH || treeFeature == TreeFeatures.BIRCH_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH; ++ treeType = org.bukkit.TreeType.BIRCH; // Folia - region threading + } else if (treeFeature == TreeFeatures.SUPER_BIRCH_BEES_0002) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH; ++ treeType = org.bukkit.TreeType.TALL_BIRCH; // Folia - region threading + } else if (treeFeature == TreeFeatures.SWAMP_OAK) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP; ++ treeType = org.bukkit.TreeType.SWAMP; // Folia - region threading + } else if (treeFeature == TreeFeatures.FANCY_OAK || treeFeature == TreeFeatures.FANCY_OAK_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE; ++ treeType = org.bukkit.TreeType.BIG_TREE; // Folia - region threading + } else if (treeFeature == TreeFeatures.JUNGLE_BUSH) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH; ++ treeType = org.bukkit.TreeType.JUNGLE_BUSH; // Folia - region threading + } else if (treeFeature == TreeFeatures.DARK_OAK) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK; ++ treeType = org.bukkit.TreeType.DARK_OAK; // Folia - region threading + } else if (treeFeature == TreeFeatures.MEGA_SPRUCE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD; ++ treeType = org.bukkit.TreeType.MEGA_REDWOOD; // Folia - region threading + } else if (treeFeature == TreeFeatures.MEGA_PINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE; ++ treeType = org.bukkit.TreeType.MEGA_PINE; // Folia - region threading + } else if (treeFeature == TreeFeatures.MEGA_JUNGLE_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE; ++ treeType = org.bukkit.TreeType.JUNGLE; // Folia - region threading + } else if (treeFeature == TreeFeatures.AZALEA_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA; ++ treeType = org.bukkit.TreeType.AZALEA; // Folia - region threading + } else if (treeFeature == TreeFeatures.MANGROVE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE; ++ treeType = org.bukkit.TreeType.MANGROVE; // Folia - region threading + } else if (treeFeature == TreeFeatures.TALL_MANGROVE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE; ++ treeType = org.bukkit.TreeType.TALL_MANGROVE; // Folia - region threading + } else if (treeFeature == TreeFeatures.CHERRY || treeFeature == TreeFeatures.CHERRY_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY; ++ treeType = org.bukkit.TreeType.CHERRY; // Folia - region threading + } else if (treeFeature == TreeFeatures.PALE_OAK || treeFeature == TreeFeatures.PALE_OAK_BONEMEAL) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK; ++ treeType = org.bukkit.TreeType.PALE_OAK; // Folia - region threading + } else if (treeFeature == TreeFeatures.PALE_OAK_CREAKING) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; ++ treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; // Folia - region threading + } else { + throw new IllegalArgumentException("Unknown tree generator " + treeFeature); + } ++ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(treeType); // Folia - region threading + } + // CraftBukkit end + } +diff --git a/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 41802482875bd4d4b505eb758740140de0db415a..aa7aefbc26db062e3ed731ca98229fa36a54b4ef 100644 +--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -139,7 +139,7 @@ public class PistonBaseBlock extends DirectionalBlock { + && pistonMovingBlockEntity.isExtending() + && ( + pistonMovingBlockEntity.getProgress(0.0F) < 0.5F +- || level.getGameTime() == pistonMovingBlockEntity.getLastTicked() ++ || level.getRedstoneGameTime() == pistonMovingBlockEntity.getLastTicked() // Folia - region threading + || ((ServerLevel)level).isHandlingTick() + )) { + i = 2; +diff --git a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +index ee2f8e8deb35059824b5730a1442f383dc79f01c..baf6322619bbe43ed136e01494fbf24e2f8e4604 100644 +--- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -41,9 +41,19 @@ public class PistonMovingBlockEntity extends BlockEntity { + private static final ThreadLocal NOCLIP = ThreadLocal.withInitial(() -> null); + private float progress; + private float progressO; +- private long lastTicked; ++ private long lastTicked = Long.MIN_VALUE; // Folia - region threading + private int deathTicks; + ++ // Folia start - region threading ++ @Override ++ public void updateTicks(long fromTickOffset, long fromRedstoneTimeOffset) { ++ super.updateTicks(fromTickOffset, fromRedstoneTimeOffset); ++ if (this.lastTicked != Long.MIN_VALUE) { ++ this.lastTicked += fromRedstoneTimeOffset; ++ } ++ } ++ // Folia end - region threading ++ + public PistonMovingBlockEntity(BlockPos pos, BlockState blockState) { + super(BlockEntityType.PISTON, pos, blockState); + } +@@ -150,8 +160,8 @@ public class PistonMovingBlockEntity extends BlockEntity { + + entity.setDeltaMovement(d1, d2, d3); + // Paper - EAR items stuck in slime pushed by a piston +- entity.activatedTick = Math.max(entity.activatedTick, net.minecraft.server.MinecraftServer.currentTick + 10); +- entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 10); ++ entity.activatedTick = Math.max(entity.activatedTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 10); // Folia - region threading ++ entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 10); // Folia - region threading + // Paper end + break; + } +@@ -292,7 +302,7 @@ public class PistonMovingBlockEntity extends BlockEntity { + } + + public static void tick(Level level, BlockPos pos, BlockState state, PistonMovingBlockEntity blockEntity) { +- blockEntity.lastTicked = level.getGameTime(); ++ blockEntity.lastTicked = level.getRedstoneGameTime(); // Folia - region threading + blockEntity.progressO = blockEntity.progress; + if (blockEntity.progressO >= 1.0F) { + if (level.isClientSide && blockEntity.deathTicks < 5) { +diff --git a/net/minecraft/world/level/border/WorldBorder.java b/net/minecraft/world/level/border/WorldBorder.java +index 7249292e77b4a54f1f4f707c4dc55924c96dd23f..eb0d3cc606fb7bb06871ea61c240873ed7e67bc5 100644 +--- a/net/minecraft/world/level/border/WorldBorder.java ++++ b/net/minecraft/world/level/border/WorldBorder.java +@@ -30,6 +30,8 @@ public class WorldBorder { + public static final WorldBorder.Settings DEFAULT_SETTINGS = new WorldBorder.Settings(0.0, 0.0, 0.2, 5.0, 5, 15, 5.999997E7F, 0L, 0.0); + public net.minecraft.server.level.ServerLevel world; // CraftBukkit + ++ // Folia - region threading - TODO make this shit thread-safe ++ + public boolean isWithinBounds(BlockPos pos) { + return this.isWithinBounds(pos.getX(), pos.getZ()); + } +@@ -43,16 +45,14 @@ public class WorldBorder { + } + + // Paper start - Bound treasure maps to world border +- private final BlockPos.MutableBlockPos mutPos = new BlockPos.MutableBlockPos(); ++ private static final ThreadLocal mutPos = ThreadLocal.withInitial(() -> new BlockPos.MutableBlockPos()); // Folia - region threading + + public boolean isBlockInBounds(int chunkX, int chunkZ) { +- this.mutPos.set(chunkX, 64, chunkZ); +- return this.isWithinBounds(this.mutPos); ++ return this.isWithinBounds(mutPos.get().set(chunkX, 64, chunkZ)); // Folia - region threading + } + + public boolean isChunkInBounds(int chunkX, int chunkZ) { +- this.mutPos.set(((chunkX << 4) + 15), 64, (chunkZ << 4) + 15); +- return this.isWithinBounds(this.mutPos); ++ return this.isWithinBounds(mutPos.get().set(((chunkX << 4) + 15), 64, (chunkZ << 4) + 15)); // Folia - region threading + } + // Paper end - Bound treasure maps to world border + +diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java +index 6ed51cf42b5864194d671b5b56f5b9bdf0291dc0..b85c547f281c58bf45c9062d0b886cb4ff7b386b 100644 +--- a/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -327,7 +327,7 @@ public abstract class ChunkGenerator { + } + + private static boolean tryAddReference(StructureManager structureManager, StructureStart structureStart) { +- if (structureStart.canBeReferenced()) { ++ if (structureStart.tryReference()) { // Folia - region threading + structureManager.addReference(structureStart); + return true; + } else { +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 761fdcd4a4e18f45547afd8edff44f61c6eeacb4..f83cfa85678d288ece2348aae41d315660095ad8 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -59,6 +59,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + public void tick() { + } + ++ // Folia start - region threading ++ @Override ++ public BlockEntity getTileEntity() { ++ return null; ++ } ++ // Folia end - region threading ++ + @Override + public boolean isRemoved() { + return true; +@@ -230,11 +237,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + + @Override + public void markUnsaved() { +- boolean isUnsaved = this.isUnsaved(); +- super.markUnsaved(); +- if (!isUnsaved) { +- this.unsavedListener.setUnsaved(this.chunkPos); +- } ++ super.markUnsaved(); // Folia - region threading - unsavedListener is not really use + } + + @Override +@@ -360,6 +363,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + + @Nullable + public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // Folia - region threading + // CraftBukkit end + int y = pos.getY(); + LevelChunkSection section = this.getSection(this.getSectionIndex(y)); +@@ -395,7 +399,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + } + + boolean hasBlockEntity = blockState.hasBlockEntity(); +- if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent ++ if (!this.level.isClientSide && !this.level.getCurrentWorldData().isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent // Folia - region threading + blockState.onRemove(this.level, pos, state, isMoving); + } else if (!blockState.is(block) && hasBlockEntity) { + this.removeBlockEntity(pos); +@@ -404,7 +408,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + if (!section.getBlockState(i, i1, i2).is(block)) { + return null; + } else { +- if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. ++ if (!this.level.isClientSide && doPlace && (!this.level.getCurrentWorldData().captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. // Folia - region threading + state.onPlace(this.level, pos, blockState, isMoving); + } + +@@ -459,7 +463,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + @Nullable + public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) { + // CraftBukkit start +- BlockEntity blockEntity = this.level.capturedTileEntities.get(pos); ++ BlockEntity blockEntity = this.level.getCurrentWorldData().capturedTileEntities.get(pos); // Folia - region threading + if (blockEntity == null) { + blockEntity = this.blockEntities.get(pos); + } +@@ -646,13 +650,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + + org.bukkit.World world = this.level.getWorld(); + if (world != null) { +- this.level.populating = true; ++ this.level.getCurrentWorldData().populating = true; // Folia - region threading + try { + for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) { + populator.populate(world, random, bukkitChunk); + } + } finally { +- this.level.populating = false; ++ this.level.getCurrentWorldData().populating = false; // Folia - region threading + } + } + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); +@@ -678,7 +682,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + @Override + public boolean isUnsaved() { + // Paper start - rewrite chunk system +- final long gameTime = this.level.getGameTime(); ++ final long gameTime = this.level.getRedstoneGameTime(); // Folia - region threading + if (((ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks)this.blockTicks).moonrise$isDirty(gameTime) + || ((ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks)this.fluidTicks).moonrise$isDirty(gameTime)) { + return true; +@@ -905,6 +909,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + this.ticker = ticker; + } + ++ // Folia start - region threading ++ @Override ++ public BlockEntity getTileEntity() { ++ return this.blockEntity; ++ } ++ // Folia end - region threading ++ + @Override + public void tick() { + if (!this.blockEntity.isRemoved() && this.blockEntity.hasLevel()) { +@@ -983,6 +994,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + this.ticker = ticker; + } + ++ // Folia start - region threading ++ @Override ++ public BlockEntity getTileEntity() { ++ return this.ticker == null ? null : this.ticker.getTileEntity(); ++ } ++ // Folia end - region threading ++ + @Override + public void tick() { + this.ticker.tick(); +diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +index 6b6aaeca14178b5b709e20ae13552d42217f15c0..950977f8d123f903630541ded35dd86a1889240f 100644 +--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -574,7 +574,7 @@ public record SerializableChunkData( + } + } + +- ChunkAccess.PackedTicks ticksForSerialization = chunk.getTicksForSerialization(level.getGameTime()); ++ ChunkAccess.PackedTicks ticksForSerialization = chunk.getTicksForSerialization(level.getRedstoneGameTime()); // Folia - region threading + ShortList[] lists = Arrays.stream(chunk.getPostProcessing()) + .map(list3 -> list3 != null ? new ShortArrayList(list3) : null) + .toArray(ShortList[]::new); +diff --git a/net/minecraft/world/level/dimension/end/EndDragonFight.java b/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 6e7e87c32734b3aae354bc34459e5f207da5c78f..2e156694b337760be986fdf1cbf863b0d896ef2d 100644 +--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -77,7 +77,7 @@ public class EndDragonFight { + .setPlayBossMusic(true) + .setCreateWorldFog(true); + public final ServerLevel level; +- private final BlockPos origin; ++ public final BlockPos origin; // Folia - region threading + public final ObjectArrayList gateways = new ObjectArrayList<>(); + private final BlockPattern exitPortalPattern; + private int ticksSinceDragonSeen; +@@ -162,7 +162,7 @@ public class EndDragonFight { + + if (!this.dragonEvent.getPlayers().isEmpty()) { + this.level.getChunkSource().addRegionTicket(TicketType.DRAGON, new ChunkPos(0, 0), 9, Unit.INSTANCE); +- boolean isArenaLoaded = this.isArenaLoaded(); ++ boolean isArenaLoaded = this.isArenaLoaded(); if (!isArenaLoaded) { return; } // Folia - region threading - don't tick if we don't own the entire region + if (this.needsStateScanning && isArenaLoaded) { + this.scanState(); + this.needsStateScanning = false; +@@ -208,6 +208,12 @@ public class EndDragonFight { + } + + List dragons = this.level.getDragons(); ++ // Folia start - region threading ++ // we do not want to deal with any dragons NOT nearby ++ dragons.removeIf((EnderDragon dragon) -> { ++ return !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(dragon); ++ }); ++ // Folia end - region threading + if (dragons.isEmpty()) { + this.dragonKilled = true; + } else { +@@ -323,8 +329,8 @@ public class EndDragonFight { + + for (int i = -8 + chunkPos.x; i <= 8 + chunkPos.x; i++) { + for (int i1 = 8 + chunkPos.z; i1 <= 8 + chunkPos.z; i1++) { +- ChunkAccess chunk = this.level.getChunk(i, i1, ChunkStatus.FULL, false); +- if (!(chunk instanceof LevelChunk)) { ++ ChunkAccess chunk = this.level.getChunkIfLoaded(i, i1); // Folia - region threading ++ if (!(chunk instanceof LevelChunk) || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, i, i1, this.level.regioniser.regionSectionChunkSize)) { + return false; + } + +@@ -496,6 +502,11 @@ public class EndDragonFight { + } + + public void onCrystalDestroyed(EndCrystal crystal, DamageSource dmgSrc) { ++ // Folia start - region threading ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, this.origin)) { ++ return; ++ } ++ // Folia end - region threading + if (this.respawnStage != null && this.respawnCrystals.contains(crystal)) { + LOGGER.debug("Aborting respawn sequence"); + this.respawnStage = null; +@@ -521,7 +532,7 @@ public class EndDragonFight { + + public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal + // Paper end - Perf: Do crystal-portal proximity check before entity lookup +- if (this.dragonKilled && this.respawnStage == null) { ++ if (this.dragonKilled && this.respawnStage == null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, this.origin)) { // Folia - region threading + BlockPos blockPos = this.portalLocation; + if (blockPos == null) { + LOGGER.debug("Tried to respawn, but need to find the portal first."); +diff --git a/net/minecraft/world/level/levelgen/PatrolSpawner.java b/net/minecraft/world/level/levelgen/PatrolSpawner.java +index 082c9b340765e3e98055a3c4444af68264a54826..9608e06c56f0aded4d6b4e9cf3d7eec348945600 100644 +--- a/net/minecraft/world/level/levelgen/PatrolSpawner.java ++++ b/net/minecraft/world/level/levelgen/PatrolSpawner.java +@@ -16,7 +16,7 @@ import net.minecraft.world.level.biome.Biome; + import net.minecraft.world.level.block.state.BlockState; + + public class PatrolSpawner implements CustomSpawner { +- private int nextTick; ++ //private int nextTick; // Folia - region threading + + @Override + public int tick(ServerLevel level, boolean spawnEnemies, boolean spawnFriendlies) { +@@ -27,6 +27,7 @@ public class PatrolSpawner implements CustomSpawner { + return 0; + } else { + RandomSource randomSource = level.random; ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading + // this.nextTick--; + // if (this.nextTick > 0) { + // return 0; +@@ -38,12 +39,12 @@ public class PatrolSpawner implements CustomSpawner { + // } else if (randomSource.nextInt(5) != 0) { + // Paper start - Pillager patrol spawn settings and per player options + // Random player selection moved up for per player spawning and configuration +- int size = level.players().size(); ++ int size = level.getLocalPlayers().size(); + if (size < 1) { + return 0; + } + +- net.minecraft.server.level.ServerPlayer player = level.players().get(randomSource.nextInt(size)); ++ net.minecraft.server.level.ServerPlayer player = level.getLocalPlayers().get(randomSource.nextInt(size)); // Folia - region threading + if (player.isSpectator()) { + return 0; + } +@@ -53,8 +54,8 @@ public class PatrolSpawner implements CustomSpawner { + --player.patrolSpawnDelay; + patrolSpawnDelay = player.patrolSpawnDelay; + } else { +- this.nextTick--; +- patrolSpawnDelay = this.nextTick; ++ worldData.patrolSpawnerNextTick--; // Folia - region threading ++ patrolSpawnDelay = worldData.patrolSpawnerNextTick; // Folia - region threading + } + if (patrolSpawnDelay > 0) { + return 0; +@@ -68,7 +69,7 @@ public class PatrolSpawner implements CustomSpawner { + if (level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { + player.patrolSpawnDelay += level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomSource.nextInt(1200); + } else { +- this.nextTick += level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomSource.nextInt(1200); ++ worldData.patrolSpawnerNextTick += level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomSource.nextInt(1200); // Folia - region threading + } + + if (days < level.paperConfig().entities.behavior.pillagerPatrols.start.day || !level.isDay()) { +diff --git a/net/minecraft/world/level/levelgen/PhantomSpawner.java b/net/minecraft/world/level/levelgen/PhantomSpawner.java +index 11d25e64349b27bf54dc1620e4cce444c79f581c..cef0474cf5f95bff717d49e58fe0a74ce6b7b345 100644 +--- a/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -19,7 +19,7 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.material.FluidState; + + public class PhantomSpawner implements CustomSpawner { +- private int nextTick; ++ //private int nextTick; // Folia - region threading + + @Override + public int tick(ServerLevel level, boolean spawnEnemies, boolean spawnFriendlies) { +@@ -34,21 +34,22 @@ public class PhantomSpawner implements CustomSpawner { + } + // Paper end - Ability to control player's insomnia and phantoms + RandomSource randomSource = level.random; +- this.nextTick--; +- if (this.nextTick > 0) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading ++ worldData.phantomSpawnerNextTick--; // Folia - region threading ++ if (worldData.phantomSpawnerNextTick > 0) { // Folia - region threading + return 0; + } else { + // Paper start - Ability to control player's insomnia and phantoms + int spawnAttemptMinSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMinSeconds; + int spawnAttemptMaxSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; +- this.nextTick += (spawnAttemptMinSeconds + randomSource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; ++ worldData.phantomSpawnerNextTick += (spawnAttemptMinSeconds + randomSource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; // Folia - region threading + // Paper end - Ability to control player's insomnia and phantoms + if (level.getSkyDarken() < 5 && level.dimensionType().hasSkyLight()) { + return 0; + } else { + int i = 0; + +- for (ServerPlayer serverPlayer : level.players()) { ++ for (ServerPlayer serverPlayer : level.getLocalPlayers()) { // Folia - region threading + if (!serverPlayer.isSpectator() && (!level.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !serverPlayer.isCreative())) { // Paper - Add phantom creative and insomniac controls + BlockPos blockPos = serverPlayer.blockPosition(); + if (!level.dimensionType().hasSkyLight() || blockPos.getY() >= level.getSeaLevel() && level.canSeeSky(blockPos)) { +diff --git a/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java b/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java +index 1f7005b01b56929fb694b69b37143b8d8c7b2898..f96fc1391167dea48cac1caa464b9026657df89a 100644 +--- a/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java ++++ b/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java +@@ -47,7 +47,7 @@ public class EndPlatformFeature extends Feature { + + // CraftBukkit start + // SPIGOT-7746: Entity will only be null during world generation, which is async, so just generate without event +- if (entity != null) { ++ if (false) { // Folia - region threading + org.bukkit.World bworld = level.getLevel().getWorld(); + org.bukkit.event.world.PortalCreateEvent portalEvent = new org.bukkit.event.world.PortalCreateEvent((java.util.List) (java.util.List) blockList.getList(), bworld, entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM); + level.getLevel().getCraftServer().getPluginManager().callEvent(portalEvent); +diff --git a/net/minecraft/world/level/levelgen/structure/StructureStart.java b/net/minecraft/world/level/levelgen/structure/StructureStart.java +index 4dafa79dd4ec55a443ba3731a79e7cd6e8052f48..743b13693c8ef1d69751de42e9c6dadefe56395c 100644 +--- a/net/minecraft/world/level/levelgen/structure/StructureStart.java ++++ b/net/minecraft/world/level/levelgen/structure/StructureStart.java +@@ -26,7 +26,7 @@ public final class StructureStart { + private final Structure structure; + private final PiecesContainer pieceContainer; + private final ChunkPos chunkPos; +- private int references; ++ private final java.util.concurrent.atomic.AtomicInteger references; // Folia - region threading + @Nullable + private volatile BoundingBox cachedBoundingBox; + +@@ -39,7 +39,7 @@ public final class StructureStart { + public StructureStart(Structure structure, ChunkPos chunkPos, int references, PiecesContainer pieceContainer) { + this.structure = structure; + this.chunkPos = chunkPos; +- this.references = references; ++ this.references = new java.util.concurrent.atomic.AtomicInteger(references); // Folia - region threading + this.pieceContainer = pieceContainer; + } + +@@ -126,7 +126,7 @@ public final class StructureStart { + compoundTag.putString("id", context.registryAccess().lookupOrThrow(Registries.STRUCTURE).getKey(this.structure).toString()); + compoundTag.putInt("ChunkX", chunkPos.x); + compoundTag.putInt("ChunkZ", chunkPos.z); +- compoundTag.putInt("references", this.references); ++ compoundTag.putInt("references", this.references.get()); // Folia - region threading + compoundTag.put("Children", this.pieceContainer.save(context)); + return compoundTag; + } else { +@@ -144,15 +144,29 @@ public final class StructureStart { + } + + public boolean canBeReferenced() { +- return this.references < this.getMaxReferences(); ++ throw new UnsupportedOperationException("Use tryReference()"); // Folia - region threading + } + ++ // Folia start - region threading ++ public boolean tryReference() { ++ for (int curr = this.references.get();;) { ++ if (curr >= this.getMaxReferences()) { ++ return false; ++ } ++ ++ if (curr == (curr = this.references.compareAndExchange(curr, curr + 1))) { ++ return true; ++ } // else: try again ++ } ++ } ++ // Folia end - region threading ++ + public void addReference() { +- this.references++; ++ throw new UnsupportedOperationException("Use tryReference()"); // Folia - region threading + } + + public int getReferences() { +- return this.references; ++ return this.references.get(); // Folia - region threading + } + + protected int getMaxReferences() { +diff --git a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +index 028eae2f9a459b60e92f3344091083aa93b54485..e7ea9df8f404a6176435204a91edeefab8070c89 100644 +--- a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java ++++ b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +@@ -47,6 +47,7 @@ public class CollectingNeighborUpdater implements NeighborUpdater { + } + + private void addAndRun(BlockPos pos, CollectingNeighborUpdater.NeighborUpdates updates) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((net.minecraft.server.level.ServerLevel)this.level, pos, "Adding block without owning region"); // Folia - region threading + boolean flag = this.count > 0; + boolean flag1 = this.maxChainedNeighborUpdates >= 0 && this.count >= this.maxChainedNeighborUpdates; + this.count++; +diff --git a/net/minecraft/world/level/saveddata/SavedData.java b/net/minecraft/world/level/saveddata/SavedData.java +index b681a5ca1c4215d5afcc988c169e22a84996a88d..3879127f6c4a7977176bcea7ccc21561210addc6 100644 +--- a/net/minecraft/world/level/saveddata/SavedData.java ++++ b/net/minecraft/world/level/saveddata/SavedData.java +@@ -8,7 +8,7 @@ import net.minecraft.nbt.NbtUtils; + import net.minecraft.util.datafix.DataFixTypes; + + public abstract class SavedData { +- private boolean dirty; ++ private volatile boolean dirty; // Folia - make map data thread-safe + + public abstract CompoundTag save(CompoundTag tag, HolderLookup.Provider registries); + +@@ -26,9 +26,10 @@ public abstract class SavedData { + + public CompoundTag save(HolderLookup.Provider registries) { + CompoundTag compoundTag = new CompoundTag(); ++ this.setDirty(false); // Folia - make map data thread-safe - move before save, so that any changes after are not lost + compoundTag.put("data", this.save(new CompoundTag(), registries)); + NbtUtils.addCurrentDataVersion(compoundTag); +- this.setDirty(false); ++ // Folia - make map data thread-safe - move before save, so that any changes after are not lost + return compoundTag; + } + +diff --git a/net/minecraft/world/level/saveddata/maps/MapIndex.java b/net/minecraft/world/level/saveddata/maps/MapIndex.java +index ffe604f8397a002800e6ecc2f878d0f6f1c98703..7ee324c32efe1e63d310120e468a2f0d8ca262b4 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapIndex.java ++++ b/net/minecraft/world/level/saveddata/maps/MapIndex.java +@@ -34,17 +34,21 @@ public class MapIndex extends SavedData { + + @Override + public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { ++ synchronized (this.usedAuxIds) { // Folia - make map data thread-safe + for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { + tag.putInt(entry.getKey(), entry.getIntValue()); + } ++ } // Folia - make map data thread-safe + + return tag; + } + + public MapId getFreeAuxValueForMap() { ++ synchronized (this.usedAuxIds) { // Folia - make map data thread-safe + int i = this.usedAuxIds.getInt("map") + 1; + this.usedAuxIds.put("map", i); + this.setDirty(); + return new MapId(i); ++ } // Folia - make map data thread-safe + } + } +diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 3c1c89aade5ff092b880ba1bf1de83f54d3d62cc..439d850053c35ba92ccd8ffbd177c6b9b75f00db 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -201,7 +201,7 @@ public class MapItemSavedData extends SavedData { + } + + @Override +- public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { ++ public synchronized CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { // Folia - make map data thread-safe + ResourceLocation.CODEC + .encodeStart(NbtOps.INSTANCE, this.dimension.location()) + .resultOrPartial(LOGGER::error) +@@ -244,7 +244,7 @@ public class MapItemSavedData extends SavedData { + return tag; + } + +- public MapItemSavedData locked() { ++ public synchronized MapItemSavedData locked() { // Folia - make map data thread-safe + MapItemSavedData mapItemSavedData = new MapItemSavedData( + this.centerX, this.centerZ, this.scale, this.trackingPosition, this.unlimitedTracking, true, this.dimension + ); +@@ -255,7 +255,7 @@ public class MapItemSavedData extends SavedData { + return mapItemSavedData; + } + +- public MapItemSavedData scaled() { ++ public synchronized MapItemSavedData scaled() { // Folia - make map data thread-safe + return createFresh(this.centerX, this.centerZ, (byte)Mth.clamp(this.scale + 1, 0, 4), this.trackingPosition, this.unlimitedTracking, this.dimension); + } + +@@ -264,7 +264,8 @@ public class MapItemSavedData extends SavedData { + return itemStack -> itemStack == stack || itemStack.is(stack.getItem()) && Objects.equals(mapId, itemStack.get(DataComponents.MAP_ID)); + } + +- public void tickCarriedBy(Player player, ItemStack mapStack) { ++ public synchronized void tickCarriedBy(Player player, ItemStack mapStack) { // Folia - make map data thread-safe ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(player, "Ticking map player in incorrect region"); // Folia - region threading + if (!this.carriedByPlayers.containsKey(player)) { + MapItemSavedData.HoldingPlayer holdingPlayer = new MapItemSavedData.HoldingPlayer(player); + this.carriedByPlayers.put(player, holdingPlayer); +@@ -413,7 +414,7 @@ public class MapItemSavedData extends SavedData { + + private byte calculateRotation(@Nullable LevelAccessor level, double yRot) { + if (this.dimension == Level.NETHER && level != null) { +- int i = (int)(level.getLevelData().getDayTime() / 10L); ++ int i = (int)(level.dayTime() / 10L); // Folia - region threading + return (byte)(i * i * 34187121 + i * 121 >> 15 & 15); + } else { + double d = yRot < 0.0 ? yRot - 8.0 : yRot + 8.0; +@@ -447,25 +448,27 @@ public class MapItemSavedData extends SavedData { + } + + @Nullable +- public Packet getUpdatePacket(MapId mapId, Player player) { ++ public synchronized Packet getUpdatePacket(MapId mapId, Player player) { // Folia - make map data thread-safe + MapItemSavedData.HoldingPlayer holdingPlayer = this.carriedByPlayers.get(player); + return holdingPlayer == null ? null : holdingPlayer.nextUpdatePacket(mapId); + } + +- public void setColorsDirty(int x, int z) { +- this.setDirty(); ++ public synchronized void setColorsDirty(int x, int z) { // Folia - make map data thread-safe ++ //this.setDirty(); // Folia - make dirty only after updating data - moved down + + for (MapItemSavedData.HoldingPlayer holdingPlayer : this.carriedBy) { + holdingPlayer.markColorsDirty(x, z); + } ++ this.setDirty(); // Folia - make dirty only after updating data - moved from above + } + +- public void setDecorationsDirty() { +- this.setDirty(); ++ public synchronized void setDecorationsDirty() { // Folia - make map data thread-safe ++ //this.setDirty(); // Folia - make dirty only after updating data - moved down + this.carriedBy.forEach(MapItemSavedData.HoldingPlayer::markDecorationsDirty); ++ this.setDirty(); // Folia - make dirty only after updating data - moved from above + } + +- public MapItemSavedData.HoldingPlayer getHoldingPlayer(Player player) { ++ public synchronized MapItemSavedData.HoldingPlayer getHoldingPlayer(Player player) { // Folia - make map data thread-safe + MapItemSavedData.HoldingPlayer holdingPlayer = this.carriedByPlayers.get(player); + if (holdingPlayer == null) { + holdingPlayer = new MapItemSavedData.HoldingPlayer(player); +@@ -476,7 +479,7 @@ public class MapItemSavedData extends SavedData { + return holdingPlayer; + } + +- public boolean toggleBanner(LevelAccessor accessor, BlockPos pos) { ++ public synchronized boolean toggleBanner(LevelAccessor accessor, BlockPos pos) { // Folia - make map data thread-safe + double d = pos.getX() + 0.5; + double d1 = pos.getZ() + 0.5; + int i = 1 << this.scale; +@@ -484,7 +487,7 @@ public class MapItemSavedData extends SavedData { + double d3 = (d1 - this.centerZ) / i; + int i1 = 63; + if (d2 >= -63.0 && d3 >= -63.0 && d2 <= 63.0 && d3 <= 63.0) { +- MapBanner mapBanner = MapBanner.fromWorld(accessor, pos); ++ MapBanner mapBanner = accessor.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(accessor.getMinecraftWorld(), pos) ? null : MapBanner.fromWorld(accessor, pos); // Folia - make map data thread-safe - don't sync load or read data we do not own + if (mapBanner == null) { + return false; + } +@@ -504,7 +507,7 @@ public class MapItemSavedData extends SavedData { + return false; + } + +- public void checkBanners(BlockGetter reader, int x, int z) { ++ public synchronized void checkBanners(BlockGetter reader, int x, int z) { // Folia - make map data thread-safe + Iterator iterator = this.bannerMarkers.values().iterator(); + + while (iterator.hasNext()) { +@@ -523,13 +526,13 @@ public class MapItemSavedData extends SavedData { + return this.bannerMarkers.values(); + } + +- public void removedFromFrame(BlockPos pos, int entityId) { ++ public synchronized void removedFromFrame(BlockPos pos, int entityId) { // Folia - make map data thread-safe + this.removeDecoration(getFrameKey(entityId)); + this.frameMarkers.remove(MapFrame.frameId(pos)); + this.setDirty(); + } + +- public boolean updateColor(int x, int z, byte color) { ++ public synchronized boolean updateColor(int x, int z, byte color) { // Folia - make map data thread-safe + byte b = this.colors[x + z * 128]; + if (b != color) { + this.setColor(x, z, color); +@@ -539,12 +542,12 @@ public class MapItemSavedData extends SavedData { + } + } + +- public void setColor(int x, int z, byte color) { ++ public synchronized void setColor(int x, int z, byte color) { // Folia - make map data thread-safe + this.colors[x + z * 128] = color; + this.setColorsDirty(x, z); + } + +- public boolean isExplorationMap() { ++ public synchronized boolean isExplorationMap() { // Folia - make map data thread-safe + for (MapDecoration mapDecoration : this.decorations.values()) { + if (mapDecoration.type().value().explorationMapElement()) { + return true; +@@ -554,7 +557,7 @@ public class MapItemSavedData extends SavedData { + return false; + } + +- public void addClientSideDecorations(List decorations) { ++ public synchronized void addClientSideDecorations(List decorations) { // Folia - make map data thread-safe + this.decorations.clear(); + this.trackedDecorationCount = 0; + +@@ -571,7 +574,7 @@ public class MapItemSavedData extends SavedData { + return this.decorations.values(); + } + +- public boolean isTrackedCountOverLimit(int trackedCount) { ++ public synchronized boolean isTrackedCountOverLimit(int trackedCount) { // Folia - make map data thread-safe + return this.trackedDecorationCount >= trackedCount; + } + +@@ -726,11 +729,13 @@ public class MapItemSavedData extends SavedData { + } + + public void applyToMap(MapItemSavedData savedData) { ++ synchronized (savedData) { // Folia - make map data thread-safe + for (int i = 0; i < this.width; i++) { + for (int i1 = 0; i1 < this.height; i1++) { + savedData.setColor(this.startX + i, this.startY + i1, this.mapColors[i + i1 * this.width]); + } + } ++ } // Folia - make map data thread-safe + } + } + } +diff --git a/net/minecraft/world/level/storage/DimensionDataStorage.java b/net/minecraft/world/level/storage/DimensionDataStorage.java +index d9a3b5a2e6495b7e22c114506c2bd1e406f58f8f..ab572ac18fd02306210c87eb9ba5e5d4197ff997 100644 +--- a/net/minecraft/world/level/storage/DimensionDataStorage.java ++++ b/net/minecraft/world/level/storage/DimensionDataStorage.java +@@ -51,6 +51,7 @@ public class DimensionDataStorage implements AutoCloseable { + } + + public T computeIfAbsent(SavedData.Factory factory, String name) { ++ synchronized (this.cache) { // Folia - make map data thread-safe + T savedData = this.get(factory, name); + if (savedData != null) { + return savedData; +@@ -59,10 +60,12 @@ public class DimensionDataStorage implements AutoCloseable { + this.set(name, savedData1); + return savedData1; + } ++ } // Folia - make map data thread-safe + } + + @Nullable + public T get(SavedData.Factory factory, String name) { ++ synchronized (this.cache) { // Folia - make map data thread-safe + Optional optional = this.cache.get(name); + if (optional == null) { + optional = Optional.ofNullable(this.readSavedData(factory.deserializer(), factory.type(), name)); +@@ -70,6 +73,7 @@ public class DimensionDataStorage implements AutoCloseable { + } + + return (T)optional.orElse(null); ++ } // Folia - make map data thread-safe + } + + @Nullable +@@ -88,8 +92,10 @@ public class DimensionDataStorage implements AutoCloseable { + } + + public void set(String name, SavedData savedData) { ++ synchronized (this.cache) { // Folia - make map data thread-safe + this.cache.put(name, Optional.of(savedData)); + savedData.setDirty(); ++ } // Folia - make map data thread-safe + } + + public CompoundTag readTagFromDisk(String filename, DataFixTypes dataFixType, int version) throws IOException { +diff --git a/net/minecraft/world/ticks/LevelChunkTicks.java b/net/minecraft/world/ticks/LevelChunkTicks.java +index faf45ac459f7c25309d6ef6dce371d484a0dae7b..8a98064f2e44b27947c1af9c80ae0d7a397db7e4 100644 +--- a/net/minecraft/world/ticks/LevelChunkTicks.java ++++ b/net/minecraft/world/ticks/LevelChunkTicks.java +@@ -48,6 +48,21 @@ public class LevelChunkTicks implements SerializableTickContainer, TickCon + this.dirty = false; + } + // Paper end - rewrite chunk system ++ // Folia start - region threading ++ public void offsetTicks(final long offset) { ++ if (offset == 0 || this.tickQueue.isEmpty()) { ++ return; ++ } ++ final ScheduledTick[] queue = this.tickQueue.toArray(new ScheduledTick[0]); ++ this.tickQueue.clear(); ++ for (final ScheduledTick entry : queue) { ++ final ScheduledTick newEntry = new ScheduledTick<>( ++ entry.type(), entry.pos(), entry.triggerTick() + offset, entry.subTickOrder() ++ ); ++ this.tickQueue.add(newEntry); ++ } ++ } ++ // Folia end - region threading + + public LevelChunkTicks() { + } +diff --git a/net/minecraft/world/ticks/LevelTicks.java b/net/minecraft/world/ticks/LevelTicks.java +index 66abc2e7adee60fa98eed1ba36e018814fd02cad..2caedf1c12e5a388f7b14989310a2137bc1117c3 100644 +--- a/net/minecraft/world/ticks/LevelTicks.java ++++ b/net/minecraft/world/ticks/LevelTicks.java +@@ -39,12 +39,69 @@ public class LevelTicks implements LevelTickAccess { + private final List> alreadyRunThisTick = new ArrayList<>(); + private final Set> toRunThisTickSet = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH); + private final BiConsumer, ScheduledTick> chunkScheduleUpdater = (levelChunkTicks, scheduledTick) -> { +- if (scheduledTick.equals(levelChunkTicks.peek())) { +- this.updateContainerScheduling(scheduledTick); ++ if (scheduledTick.equals(levelChunkTicks.peek())) { // Folia - diff on change ++ this.updateContainerScheduling(scheduledTick); // Folia - diff on change + } + }; + +- public LevelTicks(LongPredicate tickCheck) { ++ // Folia start - region threading ++ public final net.minecraft.server.level.ServerLevel world; ++ public final boolean isBlock; ++ ++ public void merge(final LevelTicks into, final long tickOffset) { ++ // note: containersToTick, toRunThisTick, alreadyRunThisTick, toRunThisTickSet ++ // are all transient state, only ever non-empty during tick. But merging regions occurs while there ++ // is no tick happening, so we assume they are empty. ++ for (final java.util.Iterator>> iterator = ++ ((Long2ObjectOpenHashMap>)this.allContainers).long2ObjectEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2ObjectMap.Entry> entry = iterator.next(); ++ final LevelChunkTicks tickContainer = entry.getValue(); ++ tickContainer.offsetTicks(tickOffset); ++ into.allContainers.put(entry.getLongKey(), tickContainer); ++ } ++ for (final java.util.Iterator iterator = ((Long2LongOpenHashMap)this.nextTickForContainer).long2LongEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2LongMap.Entry entry = iterator.next(); ++ into.nextTickForContainer.put(entry.getLongKey(), entry.getLongValue() + tickOffset); ++ } ++ } ++ ++ public void split(final int chunkToRegionShift, ++ final it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap> regionToData) { ++ for (final java.util.Iterator>> iterator = ++ ((Long2ObjectOpenHashMap>)this.allContainers).long2ObjectEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2ObjectMap.Entry> entry = iterator.next(); ++ ++ final long chunkKey = entry.getLongKey(); ++ final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkKey); ++ final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkKey); ++ ++ final long regionSectionKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey( ++ chunkX >> chunkToRegionShift, chunkZ >> chunkToRegionShift ++ ); ++ // Should always be non-null, since containers are removed on unload. ++ regionToData.get(regionSectionKey).allContainers.put(chunkKey, entry.getValue()); ++ } ++ for (final java.util.Iterator iterator = ((Long2LongOpenHashMap)this.nextTickForContainer).long2LongEntrySet().fastIterator(); ++ iterator.hasNext();) { ++ final Long2LongMap.Entry entry = iterator.next(); ++ final long chunkKey = entry.getLongKey(); ++ final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkKey); ++ final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkKey); ++ ++ final long regionSectionKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey( ++ chunkX >> chunkToRegionShift, chunkZ >> chunkToRegionShift ++ ); ++ ++ // Should always be non-null, since containers are removed on unload. ++ regionToData.get(regionSectionKey).nextTickForContainer.put(chunkKey, entry.getLongValue()); ++ } ++ } ++ // Folia end - region threading ++ ++ public LevelTicks(LongPredicate tickCheck, net.minecraft.server.level.ServerLevel world, boolean isBlock) { this.world = world; this.isBlock = isBlock; // Folia - add world and isBlock + this.tickCheck = tickCheck; + } + +@@ -56,7 +113,17 @@ public class LevelTicks implements LevelTickAccess { + this.nextTickForContainer.put(packedChunkPos, scheduledTick.triggerTick()); + } + +- chunkTicks.setOnTickAdded(this.chunkScheduleUpdater); ++ // Folia start - region threading ++ final boolean isBlock = this.isBlock; ++ final net.minecraft.server.level.ServerLevel world = this.world; ++ // make sure the lambda contains no reference to this LevelTicks ++ chunkTicks.setOnTickAdded((LevelChunkTicks levelChunkTicks, ScheduledTick tick) -> { ++ if (tick.equals(levelChunkTicks.peek())) { ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); ++ ((LevelTicks)(isBlock ? worldData.getBlockLevelTicks() : worldData.getFluidLevelTicks())).updateContainerScheduling(tick); ++ } ++ }); ++ // Folia end - region threading + } + + public void removeContainer(ChunkPos chunkPos) { +@@ -70,6 +137,7 @@ public class LevelTicks implements LevelTickAccess { + + @Override + public void schedule(ScheduledTick tick) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, tick.pos(), "Cannot schedule tick for another region!"); // Folia - region threading + long packedChunkPos = ChunkPos.asLong(tick.pos()); + LevelChunkTicks levelChunkTicks = this.allContainers.get(packedChunkPos); + if (levelChunkTicks == null) { diff --git a/folia-server/minecraft-patches/features/0001-Max-pending-logins.patch b/folia-server/minecraft-patches/features/0002-Max-pending-logins.patch similarity index 96% rename from folia-server/minecraft-patches/features/0001-Max-pending-logins.patch rename to folia-server/minecraft-patches/features/0002-Max-pending-logins.patch index e949319..c5eadfb 100644 --- a/folia-server/minecraft-patches/features/0001-Max-pending-logins.patch +++ b/folia-server/minecraft-patches/features/0002-Max-pending-logins.patch @@ -19,7 +19,7 @@ index 6173f704b0d093813ec67eb231c75be49a462e7d..159f2f169d26b436a70006f7bc9bdc48 // CraftBukkit start diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index 65835fa09cdcd3bb158025f7d8b3cb29ac8b2549..bcfe3af0b0df25d4b308b11a64c361da1ab3eeca 100644 +index 7b13a9e7d38efe7786023747f55ebf5a2ba80688..b5e609a81ee0445226b61ba5bed69ad9f100f7af 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -151,6 +151,17 @@ public abstract class PlayerList { diff --git a/folia-server/minecraft-patches/features/0002-Add-chunk-system-throughput-counters-to-tps.patch b/folia-server/minecraft-patches/features/0003-Add-chunk-system-throughput-counters-to-tps.patch similarity index 98% rename from folia-server/minecraft-patches/features/0002-Add-chunk-system-throughput-counters-to-tps.patch rename to folia-server/minecraft-patches/features/0003-Add-chunk-system-throughput-counters-to-tps.patch index 2776532..33c1275 100644 --- a/folia-server/minecraft-patches/features/0002-Add-chunk-system-throughput-counters-to-tps.patch +++ b/folia-server/minecraft-patches/features/0003-Add-chunk-system-throughput-counters-to-tps.patch @@ -58,7 +58,7 @@ index 6ab353b0d2465c3680bb3c8d0852ba0f65c00fd2..4ad647a9f98cf1c11c45f85edcba3c29 chunk = wrappedFull.getWrapped(); } else { diff --git a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java -index 3bcb1dc98c61e025874cc9e008faa722581a530c..012d3a7da7fe483393a0888c823bd2e78f5c3908 100644 +index ebe2592a78e996fa2d415663bd6436effec1ca29..5e6b490ee58a90fd7c02fa09093830c0d9c67f6b 100644 --- a/io/papermc/paper/threadedregions/commands/CommandServerHealth.java +++ b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java @@ -170,6 +170,9 @@ public final class CommandServerHealth extends Command { diff --git a/folia-server/minecraft-patches/features/0003-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch b/folia-server/minecraft-patches/features/0004-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch similarity index 98% rename from folia-server/minecraft-patches/features/0003-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch rename to folia-server/minecraft-patches/features/0004-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch index 0e15d7c..fb4fb9b 100644 --- a/folia-server/minecraft-patches/features/0003-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch +++ b/folia-server/minecraft-patches/features/0004-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch @@ -9,7 +9,7 @@ add explicit block update suppression techniques, it's better than the server crashing. diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index d36b5ad7b386391e617895a33b919e29266220e2..e16a824488d2b43c430f12b8416fdeb590e66d28 100644 +index db4ce98706bf69dcd8144faba1780f83ca1f6787..4adc38a633180cdc04523d46f5e0932f1c60f411 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -2053,7 +2053,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/folia-server/minecraft-patches/features/0004-Block-reading-in-world-tile-entities-on-worldgen-thr.patch b/folia-server/minecraft-patches/features/0005-Block-reading-in-world-tile-entities-on-worldgen-thr.patch similarity index 100% rename from folia-server/minecraft-patches/features/0004-Block-reading-in-world-tile-entities-on-worldgen-thr.patch rename to folia-server/minecraft-patches/features/0005-Block-reading-in-world-tile-entities-on-worldgen-thr.patch diff --git a/folia-server/minecraft-patches/features/0005-Sync-vehicle-position-to-player-position-on-player-d.patch b/folia-server/minecraft-patches/features/0006-Sync-vehicle-position-to-player-position-on-player-d.patch similarity index 95% rename from folia-server/minecraft-patches/features/0005-Sync-vehicle-position-to-player-position-on-player-d.patch rename to folia-server/minecraft-patches/features/0006-Sync-vehicle-position-to-player-position-on-player-d.patch index 221433d..ebb5a8a 100644 --- a/folia-server/minecraft-patches/features/0005-Sync-vehicle-position-to-player-position-on-player-d.patch +++ b/folia-server/minecraft-patches/features/0006-Sync-vehicle-position-to-player-position-on-player-d.patch @@ -7,7 +7,7 @@ This allows the player to be re-positioned before logging into the world without causing thread checks to trip on Folia. diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index df0c10880c5aea110a4eade57d257d3a46e8c180..ca3770e9f77e583dfa6cef8ca884eaf6a43f5ffa 100644 +index f5615c7f7127edda460db9158d6bd4ddad9193f7..423534a1ff02bd0d0f9baacfe2428f45c7d9acb9 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -775,8 +775,18 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc diff --git a/folia-server/minecraft-patches/features/0006-Region-profiler.patch b/folia-server/minecraft-patches/features/0007-Region-profiler.patch similarity index 99% rename from folia-server/minecraft-patches/features/0006-Region-profiler.patch rename to folia-server/minecraft-patches/features/0007-Region-profiler.patch index a2283e8..5b8d0d7 100644 --- a/folia-server/minecraft-patches/features/0006-Region-profiler.patch +++ b/folia-server/minecraft-patches/features/0007-Region-profiler.patch @@ -1544,7 +1544,7 @@ index 9c9de462eb7187d6cc3562c796e3bcf69fb20783..faf72dd6dff74296c73cb058aaabd1f9 //this.playerList.tick(); // Folia - region threading if (SharedConstants.IS_RUNNING_IN_IDE && this.tickRateManager.runsNormally()) { diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 17d124acb7149f6c19f9295ad173108d5070e55b..4bdb05948e9102304364f3681ce353f1cf2a0aee 100644 +index 329e57af5cbd38425e80dba96eb972fdfb0ce5ce..5c507be097051de9a43a31bbc6190c3db7688667 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -410,12 +410,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -1838,7 +1838,7 @@ index ce310e2dc1bb46e17143bffc5e6cec7d43a0389d..d34ad333b6ea3855a24a58fcd80ccf19 } diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index bcfe3af0b0df25d4b308b11a64c361da1ab3eeca..422e4cd1606a612056ae335d92786d8a5c46fa1d 100644 +index b5e609a81ee0445226b61ba5bed69ad9f100f7af..548f7a2b0b020ff7fff27396942cc0bc9e755afe 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -1163,6 +1163,7 @@ public abstract class PlayerList { @@ -1904,7 +1904,7 @@ index 49201d6664656ebe34c84c1c84b5ea4878729062..d9cc1d7e56c37d5ce92544edc10e89db } } diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index e16a824488d2b43c430f12b8416fdeb590e66d28..5ea7fdf1e337da4c207dd6a53ca942480dd31922 100644 +index 4adc38a633180cdc04523d46f5e0932f1c60f411..dafd90502937019b616ac0a79465e1dbc578cf66 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 diff --git a/folia-server/minecraft-patches/features/0007-Add-watchdog-thread.patch b/folia-server/minecraft-patches/features/0008-Add-watchdog-thread.patch similarity index 98% rename from folia-server/minecraft-patches/features/0007-Add-watchdog-thread.patch rename to folia-server/minecraft-patches/features/0008-Add-watchdog-thread.patch index 2eddd89..a613830 100644 --- a/folia-server/minecraft-patches/features/0007-Add-watchdog-thread.patch +++ b/folia-server/minecraft-patches/features/0008-Add-watchdog-thread.patch @@ -117,7 +117,7 @@ index 0000000000000000000000000000000000000000..258d82ab2c78482e1561343e8e1f81fc + } +} diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java -index a18da3f3f245031f0547efe9b52a1f2a219ef04a..056fb1ca7b07d5e713dcbd951830b14fc9025f4c 100644 +index 2fd27993b445cd8c3b517a91746c3f303a35238d..7123b3eb2f2e52946b8ef9de993a6828eb0bb6f7 100644 --- a/io/papermc/paper/threadedregions/TickRegionScheduler.java +++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java @@ -34,6 +34,13 @@ public final class TickRegionScheduler { diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java.patch deleted file mode 100644 index 0d26973..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java.patch +++ /dev/null @@ -1,23 +0,0 @@ ---- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -+++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -@@ -244,7 +_,7 @@ - created.addPlayer(parameter, type); - type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ); - -- ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; -+ //((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created; // Folia - region threading - } - } - -@@ -263,10 +_,7 @@ - - if (chunk.isEmpty()) { - NearbyPlayers.this.byChunk.remove(chunkKey); -- final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey); -- if (chunkData != null) { -- chunkData.nearbyPlayers = null; -- } -+ // Folia - region threading - } - } - } diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch deleted file mode 100644 index 05cc4f7..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/ca/spottedleaf/moonrise/paper/PaperHooks.java -+++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java -@@ -105,7 +_,7 @@ - } - - for (final EnderDragonPart part : parts) { -- if (part != entity && part.getBoundingBox().intersects(boundingBox) && (predicate == null || predicate.test(part))) { -+ if (part != entity && part.getBoundingBox().intersects(boundingBox) && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(part) && (predicate == null || predicate.test(part))) { // Folia - region threading - into.add(part); - } - } -@@ -127,7 +_,7 @@ - continue; - } - final T casted = (T)entityTypeTest.tryCast(part); -- if (casted != null && (predicate == null || predicate.test(casted))) { -+ if (casted != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(part) && (predicate == null || predicate.test(casted))) { // Folia - region threading - into.add(casted); - if (into.size() >= maxCount) { - break; diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java.patch deleted file mode 100644 index 1a77151..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java.patch +++ /dev/null @@ -1,87 +0,0 @@ ---- a/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java -+++ b/ca/spottedleaf/moonrise/paper/util/BaseChunkSystemHooks.java -@@ -80,18 +_,23 @@ - - @Override - public void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) { -- -+ // Folia start - threaded regions -+ level.regioniser.addChunk(holder.getPos().x, holder.getPos().z); -+ // Folia end - threaded regions - } - - @Override - public void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { - // Update progress listener for LevelLoadingScreen -- final net.minecraft.server.level.progress.ChunkProgressListener progressListener = level.getChunkSource().chunkMap.progressListener; -+ final net.minecraft.server.level.progress.ChunkProgressListener progressListener = null; // Folia - threaded regions - cannot schedule chunk task here; as it would create a chunkholder - if (progressListener != null) { - this.scheduleChunkTask(level, holder.getPos().x, holder.getPos().z, () -> { - progressListener.onStatusChange(holder.getPos(), null); - }); - } -+ // Folia start - threaded regions -+ level.regioniser.removeChunk(holder.getPos().x, holder.getPos().z); -+ // Folia end - threaded regions - } - - @Override -@@ -102,17 +_,13 @@ - - @Override - public void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().add( -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() -- ); -+ chunk.getLevel().getCurrentWorldData().addChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading - chunk.loadCallback(); - } - - @Override - public void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().remove( -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() -- ); -+ chunk.getLevel().getCurrentWorldData().removeChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading - chunk.unloadCallback(); - } - -@@ -124,9 +_,7 @@ - - @Override - public void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().add( -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() -- ); -+ chunk.getLevel().getCurrentWorldData().addTickingChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading - if (!((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) { - chunk.postProcessGeneration((ServerLevel)chunk.getLevel()); - } -@@ -137,24 +_,18 @@ - - @Override - public void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove( -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() -- ); -+ chunk.getLevel().getCurrentWorldData().removeTickingChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading - ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration - } - - @Override - public void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().add( -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() -- ); -+ chunk.getLevel().getCurrentWorldData().addEntityTickingChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading - } - - @Override - public void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().remove( -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() -- ); -+ chunk.getLevel().getCurrentWorldData().removeEntityTickingChunk(chunk.moonrise$getChunkAndHolder()); // Folia - region threading - } - - @Override diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java.patch deleted file mode 100644 index 976d861..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkData.java -@@ -5,7 +_,7 @@ - public final class ChunkData { - - private int referenceCount = 0; -- public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players -+ //public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players // Folia - region threading - - public ChunkData() { - diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java.patch deleted file mode 100644 index 5057fce..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java.patch +++ /dev/null @@ -1,32 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java -@@ -460,6 +_,19 @@ - return slices == null || !slices.isPreventingStatusUpdates(); - } - -+ // Folia start - region threading -+ // only appropriate to use when in shutdown, as this performs no logic hooks to properly add to world -+ public boolean addEntityForShutdownTeleportComplete(final Entity entity) { -+ final BlockPos pos = entity.blockPosition(); -+ final int sectionX = pos.getX() >> 4; -+ final int sectionY = Mth.clamp(pos.getY() >> 4, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)); -+ final int sectionZ = pos.getZ() >> 4; -+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); -+ -+ return slices.addEntity(entity, sectionY); -+ } -+ // Folia end - region threading -+ - protected void removeEntity(final Entity entity) { - final int sectionX = ((ChunkSystemEntity)entity).moonrise$getSectionX(); - final int sectionY = ((ChunkSystemEntity)entity).moonrise$getSectionY(); -@@ -986,6 +_,9 @@ - EntityLookup.this.removeEntityCallback(entity); - - this.entity.setLevelCallback(NoOpCallback.INSTANCE); -+ -+ // only AFTER full removal callbacks, so that thread checking will work. // Folia - region threading -+ EntityLookup.this.world.getCurrentWorldData().removeEntity(entity); // Folia - region threading - } - } - diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java.patch deleted file mode 100644 index cf96005..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java.patch +++ /dev/null @@ -1,36 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java -@@ -17,7 +_,7 @@ - private static final Entity[] EMPTY_ENTITY_ARRAY = new Entity[0]; - - private final ServerLevel serverWorld; -- public final ReferenceList trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker -+ // Folia - move to regionized world data - - public ServerEntityLookup(final ServerLevel world, final LevelCallback 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 - } - diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch deleted file mode 100644 index 39a405b..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -@@ -216,7 +_,7 @@ - final PlayerChunkLoaderData loader = ((ChunkSystemServerPlayer)player).moonrise$getChunkLoader(); - - if (loader == null) { -- return; -+ throw new IllegalStateException("Player is already removed from player chunk loader"); // Folia - region threading - } - - loader.remove(); -@@ -304,7 +_,7 @@ - public void tick() { - TickThread.ensureTickThread("Cannot tick player chunk loader async"); - long currTime = System.nanoTime(); -- for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { -+ for (final ServerPlayer player : new java.util.ArrayList<>(this.world.getLocalPlayers())) { // Folia - region threding - final PlayerChunkLoaderData loader = ((ChunkSystemServerPlayer)player).moonrise$getChunkLoader(); - if (loader == null || loader.removed || loader.world != this.world) { - // not our problem anymore diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java.patch deleted file mode 100644 index 758bc23..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java.patch +++ /dev/null @@ -1,42 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java -@@ -29,6 +_,39 @@ - - public static record SectionToUnload(int sectionX, int sectionZ, long order, int count) {} - -+ // Folia start - threaded regions -+ public List retrieveForCurrentRegion() { -+ final io.papermc.paper.threadedregions.ThreadedRegionizer.ThreadedRegion region = -+ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion(); -+ final io.papermc.paper.threadedregions.ThreadedRegionizer regionizer = region.regioniser; -+ final int shift = this.coordinateShift; -+ -+ final List ret = new ArrayList<>(); -+ -+ for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) { -+ final ConcurrentLong2ReferenceChainedHashTable.TableEntry 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 retrieveForAllRegions() { - final List ret = new ArrayList<>(); - diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java.patch deleted file mode 100644 index bb7ea3d..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java.patch +++ /dev/null @@ -1,391 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -56,6 +_,14 @@ - import java.util.concurrent.locks.LockSupport; - import java.util.function.Predicate; - -+// Folia start - region threading -+import io.papermc.paper.threadedregions.RegionizedServer; -+import io.papermc.paper.threadedregions.ThreadedRegionizer; -+import io.papermc.paper.threadedregions.TickRegionScheduler; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+// Folia end - region threading -+ - public final class ChunkHolderManager { - - private static final Logger LOGGER = LogUtils.getClassLogger(); -@@ -78,29 +_,83 @@ - private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); - private final ServerLevel world; - private final ChunkTaskScheduler taskScheduler; -- private long currentTick; -- -- private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); -- private final ObjectRBTreeSet 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 pendingFullLoadUpdate = new ArrayDeque<>(); -+ private final ObjectRBTreeSet 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 regionToData, -+ final ReferenceOpenHashSet 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 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 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 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 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 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 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 pendingFullLoadUpdate = this.pendingFullLoadUpdate; -- for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -- pendingFullLoadUpdate.add(changedFullStatus.get(i)); -- } -- } -+ -+ // Folia start - region threading -+ final Long2ObjectOpenHashMap> sectionToUpdates = new Long2ObjectOpenHashMap<>(); -+ final List thisRegionHolders = new ArrayList<>(); -+ -+ final int regionShift = this.world.moonrise$getRegionChunkShift(); -+ final ThreadedRegionizer.ThreadedRegion 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>> iterator = sectionToUpdates.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry> entry = iterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ -+ final int chunkX = CoordinateUtils.getChunkX(sectionKey) << regionShift; -+ final int chunkZ = CoordinateUtils.getChunkZ(sectionKey) << regionShift; -+ -+ final List 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 unloadSectionsForRegion = this.unloadQueue.retrieveForAllRegions(); -+ final List 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 pendingFullLoadUpdate = this.pendingFullLoadUpdate; -+ // Folia start - region threading -+ final HolderManagerRegionData data = this.getCurrentRegionData(); -+ if (data == null) { -+ return false; -+ } -+ final ArrayDeque 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(); - } - } diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java.patch deleted file mode 100644 index 1df42d8..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java.patch +++ /dev/null @@ -1,75 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java -@@ -122,7 +_,7 @@ - public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor compressionExecutor; - public final PrioritisedThreadPool.ExecutorGroup.ThreadPoolExecutor saveExecutor; - -- private final PrioritisedTaskQueue mainThreadExecutor = new PrioritisedTaskQueue(); -+ // Folia - regionised ticking - - public final ChunkHolderManager chunkHolderManager; - -@@ -337,14 +_,13 @@ - }; - - // this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions -- this.scheduleChunkTask(chunkX, chunkZ, crash, Priority.BLOCKING); -+ this.scheduleChunkTaskEventually(chunkX, chunkZ, crash, Priority.BLOCKING); // Folia - region threading - // so, make the main thread pick it up - ((ChunkSystemMinecraftServer)this.world.getServer()).moonrise$setChunkSystemCrash(new RuntimeException("Chunk system crash propagated from unrecoverableChunkSystemFailure", reportedException)); - } - - public boolean executeMainThreadTask() { -- TickThread.ensureTickThread("Cannot execute main thread task off-main"); -- return this.mainThreadExecutor.executeTask(); -+ throw new UnsupportedOperationException("Use regionised ticking hooks"); // Folia - regionised ticking - } - - public void raisePriority(final int x, final int z, final Priority priority) { -@@ -829,7 +_,7 @@ - */ - @Deprecated - public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final Priority priority) { -- return this.mainThreadExecutor.queueTask(run, priority); -+ throw new UnsupportedOperationException(); // Folia - regionised ticking - } - - public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -@@ -838,7 +_,7 @@ - - public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run, - final Priority priority) { -- return this.mainThreadExecutor.createTask(run, priority); -+ return io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.createChunkTask(this.world, chunkX, chunkZ, run, priority); // Folia - regionised ticking - } - - public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -@@ -847,8 +_,26 @@ - - public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final int chunkX, final int chunkZ, final Runnable run, - final Priority priority) { -- return this.mainThreadExecutor.queueTask(run, priority); -- } -+ return io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(this.world, chunkX, chunkZ, run, priority); // Folia - regionised ticking -+ } -+ -+ // Folia start - region threading -+ // this function is guaranteed to never touch the ticket lock or schedule lock -+ // yes, this IS a hack so that we can avoid deadlock due to region threading introducing the -+ // ticket lock in the schedule logic -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTaskEventually(final int chunkX, final int chunkZ, final Runnable run) { -+ return this.scheduleChunkTaskEventually(chunkX, chunkZ, run, Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask scheduleChunkTaskEventually(final int chunkX, final int chunkZ, final Runnable run, -+ final Priority priority) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createChunkTask(chunkX, chunkZ, run, priority); -+ this.world.taskQueueRegionData.pushGlobalChunkTask(() -> { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(ChunkTaskScheduler.this.world, chunkX, chunkZ, run, priority); -+ }); -+ return ret; -+ } -+ // Folia end - region threading - - public boolean halt(final boolean sync, final long maxWaitNS) { - this.radiusAwareGenExecutor.halt(); diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java.patch deleted file mode 100644 index 3f3c55f..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java.patch +++ /dev/null @@ -1,33 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -@@ -1359,10 +_,10 @@ - private void completeStatusConsumers(ChunkStatus status, final ChunkAccess chunk) { - // Update progress listener for LevelLoadingScreen - if (chunk != null) { -- final ChunkProgressListener progressListener = this.world.getChunkSource().chunkMap.progressListener; -+ final ChunkProgressListener progressListener = null; // Folia - threaded regions - if (progressListener != null) { - final ChunkStatus finalStatus = status; -- this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { -+ this.scheduler.scheduleChunkTaskEventually(this.chunkX, this.chunkZ, () -> { // Folia - threaded regions - progressListener.onStatusChange(this.vanillaChunkHolder.getPos(), finalStatus); - }); - } -@@ -1383,7 +_,7 @@ - } - - // must be scheduled to main, we do not trust the callback to not do anything stupid -- this.scheduler.scheduleChunkTask(this.chunkX, this.chunkZ, () -> { -+ this.scheduler.scheduleChunkTaskEventually(this.chunkX, this.chunkZ, () -> { // Folia - region threading - for (final Consumer 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 consumer : consumers) { - try { - consumer.accept(chunk); diff --git a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java.patch b/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java.patch deleted file mode 100644 index 4ea8dff..0000000 --- a/folia-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java -+++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java -@@ -1940,6 +_,17 @@ - - for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { - for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ // Folia start - region threading -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(world, currChunkX, currChunkZ, 4)) { -+ if (checkOnly) { -+ return true; -+ } else { -+ intoAABB.add(getBoxForChunk(currChunkX, currChunkZ)); -+ ret = true; -+ continue; -+ } -+ } -+ // Folia end - region threading - final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks); - - if (chunk == null) { diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/entity/activation/ActivationRange.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/entity/activation/ActivationRange.java.patch deleted file mode 100644 index 1d743d9..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/entity/activation/ActivationRange.java.patch +++ /dev/null @@ -1,174 +0,0 @@ ---- a/io/papermc/paper/entity/activation/ActivationRange.java -+++ b/io/papermc/paper/entity/activation/ActivationRange.java -@@ -48,33 +_,34 @@ - - private static int checkInactiveWakeup(final Entity entity) { - final Level world = entity.level(); -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - threaded regions - final SpigotWorldConfig config = world.spigotConfig; -- final long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; -+ final long inactiveFor = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() - entity.activatedTick; // Folia - threaded regions - if (entity.activationType == ActivationType.VILLAGER) { -- if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) { -- world.wakeupInactiveRemainingVillagers--; -+ if (inactiveFor > config.wakeUpInactiveVillagersEvery && worldData.wakeupInactiveRemainingVillagers > 0) { // Folia - threaded regions -+ worldData.wakeupInactiveRemainingVillagers--; // Folia - threaded regions - return config.wakeUpInactiveVillagersFor; - } - } else if (entity.activationType == ActivationType.ANIMAL) { -- if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) { -- world.wakeupInactiveRemainingAnimals--; -+ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && worldData.wakeupInactiveRemainingAnimals > 0) { // Folia - threaded regions -+ worldData.wakeupInactiveRemainingAnimals--; // Folia - threaded regions - return config.wakeUpInactiveAnimalsFor; - } - } else if (entity.activationType == ActivationType.FLYING_MONSTER) { -- if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) { -- world.wakeupInactiveRemainingFlying--; -+ if (inactiveFor > config.wakeUpInactiveFlyingEvery && worldData.wakeupInactiveRemainingFlying > 0) { // Folia - threaded regions -+ worldData.wakeupInactiveRemainingFlying--; // Folia - threaded regions - return config.wakeUpInactiveFlyingFor; - } - } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) { -- if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) { -- world.wakeupInactiveRemainingMonsters--; -+ if (inactiveFor > config.wakeUpInactiveMonstersEvery && worldData.wakeupInactiveRemainingMonsters > 0) { // Folia - threaded regions -+ worldData.wakeupInactiveRemainingMonsters--; // Folia - threaded regions - return config.wakeUpInactiveMonstersFor; - } - } - return -1; - } - -- static AABB maxBB = new AABB(0, 0, 0, 0, 0, 0); -+ //static AABB maxBB = new AABB(0, 0, 0, 0, 0, 0); // Folia - threaded regions - replaced by local variable - - /** - * These entities are excluded from Activation range checks. -@@ -122,10 +_,11 @@ - final int waterActivationRange = world.spigotConfig.waterActivationRange; - final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange; - final int villagerActivationRange = world.spigotConfig.villagerActivationRange; -- world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals); -- world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers); -- world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters); -- world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying); -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - threaded regions -+ worldData.wakeupInactiveRemainingAnimals = Math.min(worldData.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals); // Folia - threaded regions -+ worldData.wakeupInactiveRemainingVillagers = Math.min(worldData.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers); // Folia - threaded regions -+ worldData.wakeupInactiveRemainingMonsters = Math.min(worldData.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters); // Folia - threaded regions -+ worldData.wakeupInactiveRemainingFlying = Math.min(worldData.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying); // Folia - threaded regions - - int maxRange = Math.max(monsterActivationRange, animalActivationRange); - maxRange = Math.max(maxRange, raiderActivationRange); -@@ -135,30 +_,37 @@ - maxRange = Math.max(maxRange, villagerActivationRange); - maxRange = Math.min((world.spigotConfig.simulationDistance << 4) - 8, maxRange); - -- for (final Player player : world.players()) { -- player.activatedTick = MinecraftServer.currentTick; -+ for (final Player player : world.getLocalPlayers()) { // Folia - region threading -+ player.activatedTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - region threading - if (world.spigotConfig.ignoreSpectatorActivation && player.isSpectator()) { - continue; - } - - final int worldHeight = world.getHeight(); -- ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange); -- ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange); -- ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate(raiderActivationRange, worldHeight, raiderActivationRange); -- ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate(animalActivationRange, worldHeight, animalActivationRange); -- ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate(monsterActivationRange, worldHeight, monsterActivationRange); -- ActivationType.WATER.boundingBox = player.getBoundingBox().inflate(waterActivationRange, worldHeight, waterActivationRange); -- ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate(flyingActivationRange, worldHeight, flyingActivationRange); -- ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate(villagerActivationRange, worldHeight, villagerActivationRange); -+ final AABB maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange); // Folia - threaded regions -+ final AABB[] bbByType = new AABB[ActivationType.values().length]; // Folia - threaded regions -+ bbByType[ActivationType.MISC.ordinal()] = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange); // Folia - threaded regions -+ bbByType[ActivationType.RAIDER.ordinal()] = player.getBoundingBox().inflate(raiderActivationRange, worldHeight, raiderActivationRange); // Folia - threaded regions -+ bbByType[ActivationType.ANIMAL.ordinal()] = player.getBoundingBox().inflate(animalActivationRange, worldHeight, animalActivationRange); // Folia - threaded regions -+ bbByType[ActivationType.MONSTER.ordinal()] = player.getBoundingBox().inflate(monsterActivationRange, worldHeight, monsterActivationRange); // Folia - threaded regions -+ bbByType[ActivationType.WATER.ordinal()] = player.getBoundingBox().inflate(waterActivationRange, worldHeight, waterActivationRange); // Folia - threaded regions -+ bbByType[ActivationType.FLYING_MONSTER.ordinal()] = player.getBoundingBox().inflate(flyingActivationRange, worldHeight, flyingActivationRange); // Folia - threaded regions -+ bbByType[ActivationType.VILLAGER.ordinal()] = player.getBoundingBox().inflate(villagerActivationRange, worldHeight, villagerActivationRange); // Folia - threaded regions - -- final java.util.List entities = world.getEntities((Entity) null, ActivationRange.maxBB, e -> true); -+ final java.util.List 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; - } diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/redstone/RedstoneWireTurbo.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/redstone/RedstoneWireTurbo.java.patch deleted file mode 100644 index 5f6c962..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/redstone/RedstoneWireTurbo.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/io/papermc/paper/redstone/RedstoneWireTurbo.java -+++ b/io/papermc/paper/redstone/RedstoneWireTurbo.java -@@ -829,14 +_,14 @@ - j = getMaxCurrentStrength(upd, j); - int l = 0; - -- wire.shouldSignal = false; -+ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal = false; // Folia - region threading - // Unfortunately, World.isBlockIndirectlyGettingPowered is complicated, - // and I'm not ready to try to replicate even more functionality from - // elsewhere in Minecraft into this accelerator. So sadly, we must - // suffer the performance hit of this very expensive call. If there - // is consistency to what this call returns, we may be able to cache it. - final int k = worldIn.getBestNeighborSignal(upd.self); -- wire.shouldSignal = true; -+ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal = true; // Folia - region threading - - // The variable 'k' holds the maximum redstone power value of any adjacent blocks. - // If 'k' has the highest level of all neighbors, then the power level of this diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionShutdownThread.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionShutdownThread.java.patch deleted file mode 100644 index d75e4e1..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionShutdownThread.java.patch +++ /dev/null @@ -1,229 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/RegionShutdownThread.java -@@ -1,0 +_,226 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.moonrise.common.util.WorldUtil; -+import com.mojang.logging.LogUtils; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.ChunkPos; -+import org.bukkit.event.inventory.InventoryCloseEvent; -+import org.slf4j.Logger; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.concurrent.TimeUnit; -+ -+public final class RegionShutdownThread extends ca.spottedleaf.moonrise.common.util.TickThread { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ ThreadedRegionizer.ThreadedRegion shuttingDown; -+ -+ public RegionShutdownThread(final String name) { -+ super(name); -+ this.setUncaughtExceptionHandler((thread, thr) -> { -+ LOGGER.error("Error shutting down server", thr); -+ }); -+ } -+ -+ static ThreadedRegionizer.ThreadedRegion 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 region, -+ final ServerLevel world) { -+ try { -+ this.shuttingDown = region; -+ final List 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 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 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> -+ 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> -+ 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() -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedData.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedData.java.patch deleted file mode 100644 index bac587f..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedData.java.patch +++ /dev/null @@ -1,238 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/RegionizedData.java -@@ -1,0 +_,235 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.util.Validate; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.server.level.ServerLevel; -+import javax.annotation.Nullable; -+import java.util.function.Supplier; -+ -+/** -+ * Use to manage data that needs to be regionised. -+ *

    -+ * Note: 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. -+ *

    -+ *

    -+ * Note: 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. -+ *

    -+ *

    -+ * Regionised data may be world-checked. 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: -+ *

    -+ *         {@code
    -+ *         public class EntityTickList {
    -+ *             private final List 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 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 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 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 =
    -+ *                  new RegionizedData<>(null, () -> new TickTimes(), ...);
    -+ *         }
    -+ *         }
    -+ *     
    -+ * In general, it is advised that if a RegionizedData object is instantiated per world, that world checking -+ * is enabled for it by passing the world to the constructor. -+ *

    -+ */ -+public final class RegionizedData { -+ -+ private final ServerLevel world; -+ private final Supplier initialValueSupplier; -+ private final RegioniserCallback callback; -+ -+ /** -+ * Creates a regionised data holder. The provided initial value supplier may not be null, and it must -+ * never produce {@code null} values. -+ *

    -+ * 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. -+ *

    -+ *

    -+ * 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 and region, -+ * however using {@code null} for tasks to run at the end of a tick is valid since the tasks are tied to -+ * region only. -+ *

    -+ * @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 supplier, final RegioniserCallback 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 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 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. -+ *

    -+ * It is critical to note that each function is called while holding the region lock. -+ *

    -+ */ -+ public static interface RegioniserCallback { -+ -+ /** -+ * Completely merges the data in {@code from} to {@code into}. -+ *

    -+ * Calculating Tick Offsets: -+ * 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}. -+ *

    -+ *

    -+ * Critical Notes: -+ *

  • -+ *
      -+ * 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. -+ *
    -+ *
      -+ * This function may not throw any exceptions, or the server will be left in an unrecoverable state. -+ *
    -+ *
  • -+ *

    -+ * -+ * @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}. -+ *

    -+ * 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: -+ *

    -+         *         {@code
    -+         *         int chunkX = ...;
    -+         *         int chunkZ = ...;
    -+         *
    -+         *         int regionSectionX = chunkX >> chunkToRegionShift;
    -+         *         int regionSectionZ = chunkZ >> chunkToRegionShift;
    -+         *         long regionSectionKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(regionSectionX, regionSectionZ);
    -+         *         }
    -+         *     
    -+ *

    -+ *

    -+ * 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. -+ *

    -+ *

    -+ * 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. -+ *

    -+ * -+ * @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 regionToData, final ReferenceOpenHashSet dataSet -+ ); -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedServer.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedServer.java.patch deleted file mode 100644 index 013c080..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedServer.java.patch +++ /dev/null @@ -1,458 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/RegionizedServer.java -@@ -1,0 +_,455 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool; -+import ca.spottedleaf.moonrise.common.util.TickThread; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler; -+import net.minecraft.CrashReport; -+import net.minecraft.ReportedException; -+import net.minecraft.network.Connection; -+import net.minecraft.network.PacketListener; -+import net.minecraft.network.PacketSendListener; -+import net.minecraft.network.chat.Component; -+import net.minecraft.network.chat.MutableComponent; -+import net.minecraft.network.protocol.common.ClientboundDisconnectPacket; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.dedicated.DedicatedServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; -+import net.minecraft.world.level.GameRules; -+import org.bukkit.Bukkit; -+import org.slf4j.Logger; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.concurrent.CopyOnWriteArrayList; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.function.BooleanSupplier; -+ -+public final class RegionizedServer { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ private static final RegionizedServer INSTANCE = new RegionizedServer(); -+ -+ public final RegionizedTaskQueue taskQueue = new RegionizedTaskQueue(); -+ -+ private final CopyOnWriteArrayList worlds = new CopyOnWriteArrayList<>(); -+ private final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); -+ -+ private final MultiThreadedQueue 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 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> 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 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 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 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 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) { -+ -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedTaskQueue.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedTaskQueue.java.patch deleted file mode 100644 index 169eda8..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedTaskQueue.java.patch +++ /dev/null @@ -1,810 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/RegionizedTaskQueue.java -@@ -1,0 +_,807 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Priority; -+import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; -+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.util.Unit; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayDeque; -+import java.util.Iterator; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public final class RegionizedTaskQueue { -+ -+ private static final TicketType 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 globalChunkTask = new MultiThreadedQueue<>(); -+ private final ConcurrentLong2ReferenceChainedHashTable 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 regioniser = this.world.regioniser; -+ final ThreadedRegionizer.ThreadedRegion 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 regioniser, -+ final Long2ReferenceOpenHashMap> 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[] 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 queue : this.queues) { -+ ret += queue.size(); -+ } -+ -+ return ret; -+ } -+ } -+ -+ public boolean isEmpty() { -+ final ArrayDeque[] 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[] thisQueues) { -+ synchronized (target) { -+ final ArrayDeque[] otherQueues = target.queues; -+ for (int i = 0; i < thisQueues.length; ++i) { -+ final ArrayDeque fromQ = thisQueues[i]; -+ final ArrayDeque 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 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 regioniser, -+ final Long2ReferenceOpenHashMap> into) { -+ final Reference2ReferenceOpenHashMap, ArrayDeque[]> -+ 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[] thisQueues = this.queues; -+ for (int i = 0; i < thisQueues.length; ++i) { -+ final ArrayDeque 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 -+ region = into.get(sectionKey); -+ if (region == null) { -+ throw new IllegalStateException(); -+ } -+ -+ split.computeIfAbsent(region, (keyInMap) -> { -+ final ArrayDeque[] 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, ArrayDeque[]>> -+ iterator = split.reference2ReferenceEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Reference2ReferenceMap.Entry, ArrayDeque[]> -+ 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[] 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[] 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 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); -+ } -+ } -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedWorldData.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedWorldData.java.patch deleted file mode 100644 index b17b64b..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/RegionizedWorldData.java.patch +++ /dev/null @@ -1,773 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java -@@ -1,0 +_,770 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet; -+import ca.spottedleaf.moonrise.common.list.ReferenceList; -+import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; -+import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -+import ca.spottedleaf.moonrise.common.util.TickThread; -+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; -+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.CrashReport; -+import net.minecraft.ReportedException; -+import net.minecraft.core.BlockPos; -+import net.minecraft.network.Connection; -+import net.minecraft.network.PacketSendListener; -+import net.minecraft.network.chat.Component; -+import net.minecraft.network.chat.MutableComponent; -+import net.minecraft.network.protocol.common.ClientboundDisconnectPacket; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; -+import net.minecraft.util.VisibleForDebug; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.village.VillageSiege; -+import net.minecraft.world.entity.item.ItemEntity; -+import net.minecraft.world.level.BlockEventData; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Explosion; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.NaturalSpawner; -+import net.minecraft.world.level.ServerExplosion; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.RedStoneWireBlock; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.TickingBlockEntity; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.material.Fluid; -+import net.minecraft.world.level.pathfinder.PathTypeCache; -+import net.minecraft.world.level.redstone.CollectingNeighborUpdater; -+import net.minecraft.world.level.redstone.NeighborUpdater; -+import net.minecraft.world.ticks.LevelTicks; -+import org.bukkit.craftbukkit.block.CraftBlockState; -+import org.slf4j.Logger; -+import javax.annotation.Nullable; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Collection; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import java.util.Set; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+ -+public final class RegionizedWorldData { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ private static final Entity[] EMPTY_ENTITY_ARRAY = new Entity[0]; -+ -+ public static final RegionizedData.RegioniserCallback 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 iterator = from.entityTickList.unsafeIterator(); iterator.hasNext();) { -+ into.entityTickList.add(iterator.next()); -+ } -+ for (final Iterator iterator = from.navigatingMobs.unsafeIterator(); iterator.hasNext();) { -+ into.navigatingMobs.add(iterator.next()); -+ } -+ for (final Iterator iterator = from.trackerEntities.iterator(); iterator.hasNext();) { -+ into.trackerEntities.add(iterator.next()); -+ } -+ for (final Iterator 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 iterator = from.entityTickingChunks.iterator(); iterator.hasNext();) { -+ into.entityTickingChunks.add(iterator.next()); -+ } -+ for (final Iterator iterator = from.tickingChunks.iterator(); iterator.hasNext();) { -+ into.tickingChunks.add(iterator.next()); -+ } -+ for (final Iterator 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 regionToData, -+ final ReferenceOpenHashSet 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> levelTicksBlockRegionData = new Long2ReferenceOpenHashMap<>(regionToData.size(), 0.75f); -+ final Long2ReferenceOpenHashMap> levelTicksFluidRegionData = new Long2ReferenceOpenHashMap<>(regionToData.size(), 0.75f); -+ -+ for (final Iterator> iterator = regionToData.long2ReferenceEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ReferenceMap.Entry 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 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 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 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 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 localPlayers = new ArrayList<>(); -+ private final NearbyPlayers nearbyPlayers; -+ private final ReferenceList allEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); -+ private final ReferenceList loadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); -+ private final IteratorSafeOrderedReferenceSet entityTickList = new IteratorSafeOrderedReferenceSet<>(); -+ private final IteratorSafeOrderedReferenceSet navigatingMobs = new IteratorSafeOrderedReferenceSet<>(); -+ public final ReferenceList trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker -+ public final ReferenceList trackerUnloadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker -+ -+ // block ticking -+ private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); -+ private final LevelTicks blockLevelTicks; -+ private final LevelTicks fluidLevelTicks; -+ -+ // tile entity ticking -+ private final List pendingBlockEntityTickers = new ArrayList<>(); -+ private final List 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 entityTickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDER_ARRAY); -+ private final ReferenceList tickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDER_ARRAY); -+ private final ReferenceList 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 capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper -+ public final Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper -+ public List 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 explosionDensityCache = new HashMap<>(64, 0.25f); -+ public final PathTypeCache pathTypesByPosCache = new PathTypeCache(); -+ public final List temporaryChunkTickList = new java.util.ArrayList<>(); -+ public final Set chunkHoldersToBroadcast = new ReferenceLinkedOpenHashSet<>(); -+ -+ // not transient -+ public java.util.ArrayDeque redstoneUpdateInfos; -+ -+ // Mob spawning -+ public final ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap 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 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 getLocalEntities() { -+ return this.allEntities; -+ } -+ -+ public Entity[] getLocalEntitiesCopy() { -+ return Arrays.copyOf(this.allEntities.getRawData(), this.allEntities.size(), Entity[].class); -+ } -+ -+ public List 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 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 action) { -+ final IteratorSafeOrderedReferenceSet.Iterator 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 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 blockEvents) { -+ for (final BlockEventData blockEventData : blockEvents) { -+ this.pushBlockEvent(blockEventData); -+ } -+ } -+ -+ public void removeIfBlockEvents(final Predicate predicate) { -+ for (final Iterator 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 getBlockLevelTicks() { -+ return this.blockLevelTicks; -+ } -+ -+ public LevelTicks 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 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 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 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 getChunks() { -+ return this.chunks; -+ } -+ -+ public int getEntityTickingChunkCount() { -+ return this.entityTickingChunks.size(); -+ } -+ -+ public int getChunkCount() { -+ return this.chunks.size(); -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/Schedule.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/Schedule.java.patch deleted file mode 100644 index a0f3fc9..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/Schedule.java.patch +++ /dev/null @@ -1,94 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/Schedule.java -@@ -1,0 +_,91 @@ -+package io.papermc.paper.threadedregions; -+ -+/** -+ * A Schedule is an object that can be used to maintain a periodic schedule for an event of interest. -+ */ -+public final class Schedule { -+ -+ private long lastPeriod; -+ -+ /** -+ * Initialises a schedule with the provided period. -+ * @param firstPeriod The last time an event of interest occurred. -+ * @see #setLastPeriod(long) -+ */ -+ public Schedule(final long firstPeriod) { -+ this.lastPeriod = firstPeriod; -+ } -+ -+ /** -+ * Updates the last period to the specified value. This call sets the last "time" the event -+ * of interest took place at. Thus, the value returned by {@link #getDeadline(long)} is -+ * the provided time plus the period length provided to {@code getDeadline}. -+ * @param value The value to set the last period to. -+ */ -+ public void setLastPeriod(final long value) { -+ this.lastPeriod = value; -+ } -+ -+ /** -+ * Returns the last time the event of interest should have taken place. -+ */ -+ public long getLastPeriod() { -+ return this.lastPeriod; -+ } -+ -+ /** -+ * Returns the number of times the event of interest should have taken place between the last -+ * period and the provided time given the period between each event. -+ * @param periodLength The length of the period between events in ns. -+ * @param time The provided time. -+ */ -+ public int getPeriodsAhead(final long periodLength, final long time) { -+ final long difference = time - this.lastPeriod; -+ final int ret = (int)(Math.abs(difference) / periodLength); -+ return difference >= 0 ? ret : -ret; -+ } -+ -+ /** -+ * Returns the next starting deadline for the event of interest to take place, -+ * given the provided period length. -+ * @param periodLength The provided period length. -+ */ -+ public long getDeadline(final long periodLength) { -+ return this.lastPeriod + periodLength; -+ } -+ -+ /** -+ * Adjusts the last period so that the next starting deadline returned is the next period specified, -+ * given the provided period length. -+ * @param nextPeriod The specified next starting deadline. -+ * @param periodLength The specified period length. -+ */ -+ public void setNextPeriod(final long nextPeriod, final long periodLength) { -+ this.lastPeriod = nextPeriod - periodLength; -+ } -+ -+ /** -+ * Increases the last period by the specified number of periods and period length. -+ * The specified number of periods may be < 0, in which case the last period -+ * will decrease. -+ * @param periods The specified number of periods. -+ * @param periodLength The specified period length. -+ */ -+ public void advanceBy(final int periods, final long periodLength) { -+ this.lastPeriod += (long)periods * periodLength; -+ } -+ -+ /** -+ * Sets the last period so that it is the specified number of periods ahead -+ * given the specified time and period length. -+ * @param periodsToBeAhead Specified number of periods to be ahead by. -+ * @param periodLength The specified period length. -+ * @param time The specified time. -+ */ -+ public void setPeriodsAhead(final int periodsToBeAhead, final long periodLength, final long time) { -+ final int periodsAhead = this.getPeriodsAhead(periodLength, time); -+ final int periodsToAdd = periodsToBeAhead - periodsAhead; -+ -+ this.lastPeriod -= (long)periodsToAdd * periodLength; -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TeleportUtils.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TeleportUtils.java.patch deleted file mode 100644 index b5728de..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TeleportUtils.java.patch +++ /dev/null @@ -1,85 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/TeleportUtils.java -@@ -1,0 +_,82 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.completable.CallbackCompletable; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.phys.Vec3; -+import org.bukkit.Location; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.event.player.PlayerTeleportEvent; -+import java.util.function.Consumer; -+ -+public final class TeleportUtils { -+ -+ public static void teleport(final T from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch, -+ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer onComplete) { -+ teleport(from, useFromRootVehicle, to, yaw, pitch, teleportFlags, cause, onComplete, null); -+ } -+ -+ public static void teleport(final T from, final boolean useFromRootVehicle, final Entity to, final Float yaw, final Float pitch, -+ final long teleportFlags, final PlayerTeleportEvent.TeleportCause cause, final Consumer onComplete, -+ final java.util.function.Predicate preTeleport) { -+ // retrieve coordinates -+ final CallbackCompletable positionCompletable = new CallbackCompletable<>(); -+ -+ positionCompletable.addWaiter( -+ (final Location loc, final Throwable thr) -> { -+ if (loc == null) { -+ if (onComplete != null) { -+ onComplete.accept(null); -+ } -+ return; -+ } -+ final boolean scheduled = from.getBukkitEntity().taskScheduler.schedule( -+ (final T realFrom) -> { -+ final Vec3 pos = new Vec3( -+ loc.getX(), loc.getY(), loc.getZ() -+ ); -+ if (preTeleport != null && !preTeleport.test(realFrom)) { -+ if (onComplete != null) { -+ onComplete.accept(null); -+ } -+ return; -+ } -+ (useFromRootVehicle ? realFrom.getRootVehicle() : realFrom).teleportAsync( -+ ((CraftWorld)loc.getWorld()).getHandle(), pos, null, null, null, -+ cause, teleportFlags, onComplete -+ ); -+ }, -+ (final Entity retired) -> { -+ if (onComplete != null) { -+ onComplete.accept(null); -+ } -+ }, -+ 1L -+ ); -+ if (!scheduled) { -+ if (onComplete != null) { -+ onComplete.accept(null); -+ } -+ } -+ } -+ ); -+ -+ final boolean scheduled = to.getBukkitEntity().taskScheduler.schedule( -+ (final Entity target) -> { -+ positionCompletable.complete(target.getBukkitEntity().getLocation()); -+ }, -+ (final Entity retired) -> { -+ if (onComplete != null) { -+ onComplete.accept(null); -+ } -+ }, -+ 1L -+ ); -+ if (!scheduled) { -+ if (onComplete != null) { -+ onComplete.accept(null); -+ } -+ } -+ } -+ -+ private TeleportUtils() {} -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/ThreadedRegionizer.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/ThreadedRegionizer.java.patch deleted file mode 100644 index 2eeb9b8..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/ThreadedRegionizer.java.patch +++ /dev/null @@ -1,1408 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/ThreadedRegionizer.java -@@ -1,0 +_,1405 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -+import com.destroystokyo.paper.util.SneakyThrow; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongComparator; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+import org.slf4j.Logger; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Set; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.concurrent.locks.StampedLock; -+import java.util.function.BooleanSupplier; -+import java.util.function.Consumer; -+ -+public final class ThreadedRegionizer, S extends ThreadedRegionizer.ThreadedRegionSectionData> { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ public final int regionSectionChunkSize; -+ public final int sectionChunkShift; -+ public final int minSectionRecalcCount; -+ public final int emptySectionCreateRadius; -+ public final int regionSectionMergeRadius; -+ public final double maxDeadRegionPercent; -+ public final ServerLevel world; -+ -+ private final SWMRLong2ObjectHashTable> sections = new SWMRLong2ObjectHashTable<>(); -+ private final SWMRLong2ObjectHashTable> regionsById = new SWMRLong2ObjectHashTable<>(); -+ private final RegionCallbacks callbacks; -+ private final StampedLock regionLock = new StampedLock(); -+ private Thread writeLockOwner; -+ -+ /* -+ static final record Operation(String type, int chunkX, int chunkZ) {} -+ private final MultiThreadedQueue ops = new MultiThreadedQueue<>(); -+ */ -+ -+ /* -+ * See REGION_LOGIC.md for complete details on what this class is doing -+ */ -+ -+ public ThreadedRegionizer(final int minSectionRecalcCount, final double maxDeadRegionPercent, -+ final int emptySectionCreateRadius, final int regionSectionMergeRadius, -+ final int regionSectionChunkShift, final ServerLevel world, -+ final RegionCallbacks callbacks) { -+ if (emptySectionCreateRadius <= 0) { -+ throw new IllegalStateException("Region section create radius must be > 0"); -+ } -+ if (regionSectionMergeRadius <= 0) { -+ throw new IllegalStateException("Region section merge radius must be > 0"); -+ } -+ this.regionSectionChunkSize = 1 << regionSectionChunkShift; -+ this.sectionChunkShift = regionSectionChunkShift; -+ this.minSectionRecalcCount = Math.max(2, minSectionRecalcCount); -+ this.maxDeadRegionPercent = maxDeadRegionPercent; -+ this.emptySectionCreateRadius = emptySectionCreateRadius; -+ this.regionSectionMergeRadius = regionSectionMergeRadius; -+ this.world = world; -+ this.callbacks = callbacks; -+ //this.loadTestData(); -+ } -+ -+ /* -+ private static String substr(String val, String prefix, int from) { -+ int idx = val.indexOf(prefix, from) + prefix.length(); -+ int idx2 = val.indexOf(',', idx); -+ if (idx2 == -1) { -+ idx2 = val.indexOf(']', idx); -+ } -+ return val.substring(idx, idx2); -+ } -+ -+ private void loadTestData() { -+ if (true) { -+ return; -+ } -+ try { -+ final JsonArray arr = JsonParser.parseReader(new FileReader("test.json")).getAsJsonArray(); -+ -+ List ops = new ArrayList<>(); -+ -+ for (JsonElement elem : arr) { -+ JsonObject obj = elem.getAsJsonObject(); -+ String val = obj.get("value").getAsString(); -+ -+ String type = substr(val, "type=", 0); -+ String x = substr(val, "chunkX=", 0); -+ String z = substr(val, "chunkZ=", 0); -+ -+ ops.add(new Operation(type, Integer.parseInt(x), Integer.parseInt(z))); -+ } -+ -+ for (Operation op : ops) { -+ switch (op.type) { -+ case "add": { -+ this.addChunk(op.chunkX, op.chunkZ); -+ break; -+ } -+ case "remove": { -+ this.removeChunk(op.chunkX, op.chunkZ); -+ break; -+ } -+ case "mark_ticking": { -+ this.sections.get(CoordinateUtils.getChunkKey(op.chunkX, op.chunkZ)).region.tryMarkTicking(); -+ break; -+ } -+ case "rel_region": { -+ if (this.sections.get(CoordinateUtils.getChunkKey(op.chunkX, op.chunkZ)).region.state == ThreadedRegion.STATE_TICKING) { -+ this.sections.get(CoordinateUtils.getChunkKey(op.chunkX, op.chunkZ)).region.markNotTicking(); -+ } -+ break; -+ } -+ } -+ } -+ -+ } catch (final Exception ex) { -+ throw new IllegalStateException(ex); -+ } -+ } -+ */ -+ -+ public void acquireReadLock() { -+ this.regionLock.readLock(); -+ } -+ -+ public void releaseReadLock() { -+ this.regionLock.tryUnlockRead(); -+ } -+ -+ private void acquireWriteLock() { -+ final Thread currentThread = Thread.currentThread(); -+ if (this.writeLockOwner == currentThread) { -+ throw new IllegalStateException("Cannot recursively operate in the regioniser"); -+ } -+ this.regionLock.writeLock(); -+ this.writeLockOwner = currentThread; -+ } -+ -+ private void releaseWriteLock() { -+ this.writeLockOwner = null; -+ this.regionLock.tryUnlockWrite(); -+ } -+ -+ private void onRegionCreate(final ThreadedRegion region) { -+ final ThreadedRegion conflict; -+ if ((conflict = this.regionsById.putIfAbsent(region.id, region)) != null) { -+ throw new IllegalStateException("Region " + region + " is already mapped to " + conflict); -+ } -+ } -+ -+ private void onRegionDestroy(final ThreadedRegion region) { -+ final ThreadedRegion removed = this.regionsById.remove(region.id); -+ if (removed != region) { -+ throw new IllegalStateException("Expected to remove " + region + ", but removed " + removed); -+ } -+ } -+ -+ public int getSectionCoordinate(final int chunkCoordinate) { -+ return chunkCoordinate >> this.sectionChunkShift; -+ } -+ -+ public long getSectionKey(final BlockPos pos) { -+ return CoordinateUtils.getChunkKey((pos.getX() >> 4) >> this.sectionChunkShift, (pos.getZ() >> 4) >> this.sectionChunkShift); -+ } -+ -+ public long getSectionKey(final ChunkPos pos) { -+ return CoordinateUtils.getChunkKey(pos.x >> this.sectionChunkShift, pos.z >> this.sectionChunkShift); -+ } -+ -+ public long getSectionKey(final Entity entity) { -+ final ChunkPos pos = entity.chunkPosition(); -+ return CoordinateUtils.getChunkKey(pos.x >> this.sectionChunkShift, pos.z >> this.sectionChunkShift); -+ } -+ -+ public void computeForAllRegions(final Consumer> consumer) { -+ this.regionLock.readLock(); -+ try { -+ this.regionsById.forEachValue(consumer); -+ } finally { -+ this.regionLock.tryUnlockRead(); -+ } -+ } -+ -+ public void computeForAllRegionsUnsynchronised(final Consumer> consumer) { -+ this.regionsById.forEachValue(consumer); -+ } -+ -+ public int computeForRegions(final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ, -+ final Consumer>> consumer) { -+ final int shift = this.sectionChunkShift; -+ final int fromSectionX = fromChunkX >> shift; -+ final int fromSectionZ = fromChunkZ >> shift; -+ final int toSectionX = toChunkX >> shift; -+ final int toSectionZ = toChunkZ >> shift; -+ this.acquireWriteLock(); -+ try { -+ final ReferenceOpenHashSet> set = new ReferenceOpenHashSet<>(); -+ -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final ThreadedRegionSection section = this.sections.get(CoordinateUtils.getChunkKey(currX, currZ)); -+ if (section != null) { -+ set.add(section.getRegionPlain()); -+ } -+ } -+ } -+ -+ consumer.accept(set); -+ -+ return set.size(); -+ } finally { -+ this.releaseWriteLock(); -+ } -+ } -+ -+ public ThreadedRegion getRegionAtUnsynchronised(final int chunkX, final int chunkZ) { -+ final int sectionX = chunkX >> this.sectionChunkShift; -+ final int sectionZ = chunkZ >> this.sectionChunkShift; -+ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); -+ -+ final ThreadedRegionSection section = this.sections.get(sectionKey); -+ -+ return section == null ? null : section.getRegion(); -+ } -+ -+ public ThreadedRegion getRegionAtSynchronised(final int chunkX, final int chunkZ) { -+ final int sectionX = chunkX >> this.sectionChunkShift; -+ final int sectionZ = chunkZ >> this.sectionChunkShift; -+ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); -+ -+ // try an optimistic read -+ { -+ final long readAttempt = this.regionLock.tryOptimisticRead(); -+ final ThreadedRegionSection optimisticSection = this.sections.get(sectionKey); -+ final ThreadedRegion optimisticRet = -+ optimisticSection == null ? null : optimisticSection.getRegionPlain(); -+ if (this.regionLock.validate(readAttempt)) { -+ return optimisticRet; -+ } -+ } -+ -+ // failed, fall back to acquiring the lock -+ this.regionLock.readLock(); -+ try { -+ final ThreadedRegionSection section = this.sections.get(sectionKey); -+ -+ return section == null ? null : section.getRegionPlain(); -+ } finally { -+ this.regionLock.tryUnlockRead(); -+ } -+ } -+ -+ /** -+ * Adds a chunk to the regioniser. Note that it is illegal to add a chunk unless -+ * addChunk has not been called for it or removeChunk has been previously called. -+ * -+ *

    -+ * Note that it is illegal to additionally call addChunk or removeChunk for the same -+ * region section in parallel. -+ *

    -+ */ -+ public void addChunk(final int chunkX, final int chunkZ) { -+ final int sectionX = chunkX >> this.sectionChunkShift; -+ final int sectionZ = chunkZ >> this.sectionChunkShift; -+ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); -+ -+ // Given that for each section, no addChunk/removeChunk can occur in parallel, -+ // we can avoid the lock IF the section exists AND it has a non-zero chunk count. -+ { -+ final ThreadedRegionSection existing = this.sections.get(sectionKey); -+ if (existing != null && !existing.isEmpty()) { -+ existing.addChunk(chunkX, chunkZ); -+ return; -+ } // else: just acquire the write lock -+ } -+ -+ this.acquireWriteLock(); -+ try { -+ ThreadedRegionSection section = this.sections.get(sectionKey); -+ -+ List> newSections = new ArrayList<>(); -+ -+ if (section == null) { -+ // no section at all -+ section = new ThreadedRegionSection<>(sectionX, sectionZ, this, chunkX, chunkZ); -+ this.sections.put(sectionKey, section); -+ newSections.add(section); -+ } else { -+ section.addChunk(chunkX, chunkZ); -+ } -+ // due to the fast check from above, we know the section is empty whether we needed to create it or not -+ -+ // enforce the adjacency invariant by creating / updating neighbour sections -+ final int createRadius = this.emptySectionCreateRadius; -+ final int searchRadius = createRadius + this.regionSectionMergeRadius; -+ ReferenceOpenHashSet> nearbyRegions = null; -+ for (int dx = -searchRadius; dx <= searchRadius; ++dx) { -+ for (int dz = -searchRadius; dz <= searchRadius; ++dz) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); -+ final boolean inCreateRange = squareDistance <= createRadius; -+ -+ final int neighbourX = dx + sectionX; -+ final int neighbourZ = dz + sectionZ; -+ final long neighbourKey = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); -+ -+ ThreadedRegionSection neighbourSection = this.sections.get(neighbourKey); -+ -+ if (neighbourSection != null) { -+ if (nearbyRegions == null) { -+ nearbyRegions = new ReferenceOpenHashSet<>(((searchRadius * 2 + 1) * (searchRadius * 2 + 1)) >> 1); -+ } -+ nearbyRegions.add(neighbourSection.getRegionPlain()); -+ } -+ -+ if (!inCreateRange) { -+ continue; -+ } -+ -+ // we need to ensure the section exists -+ if (neighbourSection != null) { -+ // nothing else to do -+ neighbourSection.incrementNonEmptyNeighbours(); -+ continue; -+ } -+ neighbourSection = new ThreadedRegionSection<>(neighbourX, neighbourZ, this, 1); -+ if (null != this.sections.put(neighbourKey, neighbourSection)) { -+ throw new IllegalStateException("Failed to insert new section"); -+ } -+ newSections.add(neighbourSection); -+ } -+ } -+ -+ if (newSections.isEmpty()) { -+ // if we didn't add any sections, then we don't need to merge any regions or create a region -+ return; -+ } -+ -+ final ThreadedRegion regionOfInterest; -+ final boolean regionOfInterestAlive; -+ if (nearbyRegions == null) { -+ // we can simply create a new region, don't have neighbours to worry about merging into -+ regionOfInterest = new ThreadedRegion<>(this); -+ regionOfInterestAlive = true; -+ -+ for (int i = 0, len = newSections.size(); i < len; ++i) { -+ regionOfInterest.addSection(newSections.get(i)); -+ } -+ -+ // only call create callback after adding sections -+ regionOfInterest.onCreate(); -+ } else { -+ // need to merge the regions -+ ThreadedRegion firstUnlockedRegion = null; -+ -+ for (final ThreadedRegion region : nearbyRegions) { -+ if (region.isTicking()) { -+ continue; -+ } -+ firstUnlockedRegion = region; -+ if (firstUnlockedRegion.state == ThreadedRegion.STATE_READY && (!firstUnlockedRegion.mergeIntoLater.isEmpty() || !firstUnlockedRegion.expectingMergeFrom.isEmpty())) { -+ throw new IllegalStateException("Illegal state for unlocked region " + firstUnlockedRegion); -+ } -+ break; -+ } -+ -+ if (firstUnlockedRegion != null) { -+ regionOfInterest = firstUnlockedRegion; -+ } else { -+ regionOfInterest = new ThreadedRegion<>(this); -+ } -+ -+ for (int i = 0, len = newSections.size(); i < len; ++i) { -+ regionOfInterest.addSection(newSections.get(i)); -+ } -+ -+ // only call create callback after adding sections -+ if (firstUnlockedRegion == null) { -+ regionOfInterest.onCreate(); -+ } -+ -+ if (firstUnlockedRegion != null && nearbyRegions.size() == 1) { -+ // nothing to do further, no need to merge anything -+ return; -+ } -+ -+ // we need to now tell all the other regions to merge into the region we just created, -+ // and to merge all the ones we can immediately -+ -+ for (final ThreadedRegion region : nearbyRegions) { -+ if (region == regionOfInterest) { -+ continue; -+ } -+ -+ if (!region.killAndMergeInto(regionOfInterest)) { -+ // note: the region may already be a merge target -+ regionOfInterest.mergeIntoLater(region); -+ } -+ } -+ -+ if (firstUnlockedRegion != null && firstUnlockedRegion.state == ThreadedRegion.STATE_READY) { -+ // we need to retire this region if the merges added other pending merges -+ if (!firstUnlockedRegion.mergeIntoLater.isEmpty() || !firstUnlockedRegion.expectingMergeFrom.isEmpty()) { -+ firstUnlockedRegion.state = ThreadedRegion.STATE_TRANSIENT; -+ this.callbacks.onRegionInactive(firstUnlockedRegion); -+ } -+ } -+ -+ // need to set alive if we created it and there are no pending merges -+ regionOfInterestAlive = firstUnlockedRegion == null && regionOfInterest.mergeIntoLater.isEmpty() && regionOfInterest.expectingMergeFrom.isEmpty(); -+ } -+ -+ if (regionOfInterestAlive) { -+ regionOfInterest.state = ThreadedRegion.STATE_READY; -+ if (!regionOfInterest.mergeIntoLater.isEmpty() || !regionOfInterest.expectingMergeFrom.isEmpty()) { -+ throw new IllegalStateException("Should not happen on region " + this); -+ } -+ this.callbacks.onRegionActive(regionOfInterest); -+ } -+ -+ if (regionOfInterest.state == ThreadedRegion.STATE_READY) { -+ if (!regionOfInterest.mergeIntoLater.isEmpty() || !regionOfInterest.expectingMergeFrom.isEmpty()) { -+ throw new IllegalStateException("Should not happen on region " + this); -+ } -+ } -+ } catch (final Throwable throwable) { -+ LOGGER.error("Failed to add chunk (" + chunkX + "," + chunkZ + ")", throwable); -+ SneakyThrow.sneaky(throwable); -+ return; // unreachable -+ } finally { -+ this.releaseWriteLock(); -+ } -+ } -+ -+ public void removeChunk(final int chunkX, final int chunkZ) { -+ final int sectionX = chunkX >> this.sectionChunkShift; -+ final int sectionZ = chunkZ >> this.sectionChunkShift; -+ final long sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); -+ -+ // Given that for each section, no addChunk/removeChunk can occur in parallel, -+ // we can avoid the lock IF the section exists AND it has a chunk count > 1 -+ final ThreadedRegionSection section = this.sections.get(sectionKey); -+ if (section == null) { -+ throw new IllegalStateException("Chunk (" + chunkX + "," + chunkZ + ") has no section"); -+ } -+ if (!section.hasOnlyOneChunk()) { -+ // chunk will not go empty, so we don't need to acquire the lock -+ section.removeChunk(chunkX, chunkZ); -+ return; -+ } -+ -+ this.acquireWriteLock(); -+ try { -+ section.removeChunk(chunkX, chunkZ); -+ -+ final int searchRadius = this.emptySectionCreateRadius; -+ for (int dx = -searchRadius; dx <= searchRadius; ++dx) { -+ for (int dz = -searchRadius; dz <= searchRadius; ++dz) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ -+ final int neighbourX = dx + sectionX; -+ final int neighbourZ = dz + sectionZ; -+ final long neighbourKey = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); -+ -+ final ThreadedRegionSection neighbourSection = this.sections.get(neighbourKey); -+ -+ // should be non-null here always -+ neighbourSection.decrementNonEmptyNeighbours(); -+ } -+ } -+ } catch (final Throwable throwable) { -+ LOGGER.error("Failed to add chunk (" + chunkX + "," + chunkZ + ")", throwable); -+ SneakyThrow.sneaky(throwable); -+ return; // unreachable -+ } finally { -+ this.releaseWriteLock(); -+ } -+ } -+ -+ // must hold regionLock -+ private void onRegionRelease(final ThreadedRegion region) { -+ if (!region.mergeIntoLater.isEmpty()) { -+ throw new IllegalStateException("Region " + region + " should not have any regions to merge into!"); -+ } -+ -+ final boolean hasExpectingMerges = !region.expectingMergeFrom.isEmpty(); -+ -+ // is this region supposed to merge into any other region? -+ if (hasExpectingMerges) { -+ // merge the regions into this one -+ final ReferenceOpenHashSet> expectingMergeFrom = region.expectingMergeFrom.clone(); -+ for (final ThreadedRegion mergeFrom : expectingMergeFrom) { -+ if (!mergeFrom.killAndMergeInto(region)) { -+ throw new IllegalStateException("Merge from region " + mergeFrom + " should be killable! Trying to merge into " + region); -+ } -+ } -+ -+ if (!region.expectingMergeFrom.isEmpty()) { -+ throw new IllegalStateException("Region " + region + " should no longer have merge requests after mering from " + expectingMergeFrom); -+ } -+ -+ if (!region.mergeIntoLater.isEmpty()) { -+ // There is another nearby ticking region that we need to merge into -+ region.state = ThreadedRegion.STATE_TRANSIENT; -+ this.callbacks.onRegionInactive(region); -+ // return to avoid removing dead sections or splitting, these actions will be performed -+ // by the region we merge into -+ return; -+ } -+ } -+ -+ // now check whether we need to recalculate regions -+ final boolean removeDeadSections = hasExpectingMerges || region.hasNoAliveSections() -+ || (region.sectionByKey.size() >= this.minSectionRecalcCount && region.getDeadSectionPercent() >= this.maxDeadRegionPercent); -+ final boolean removedDeadSections = removeDeadSections && !region.deadSections.isEmpty(); -+ if (removeDeadSections) { -+ // kill dead sections -+ for (final ThreadedRegionSection deadSection : region.deadSections) { -+ final long key = CoordinateUtils.getChunkKey(deadSection.sectionX, deadSection.sectionZ); -+ -+ if (!deadSection.isEmpty()) { -+ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!"); -+ } -+ if (deadSection.hasNonEmptyNeighbours()) { -+ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has non-empty neighbours!"); -+ } -+ if (!region.sectionByKey.remove(key, deadSection)) { -+ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection); -+ } -+ if (this.sections.remove(key) != deadSection) { -+ throw new IllegalStateException("Cannot remove dead section '" + -+ deadSection.toStringWithRegion() + "' from section state! State at section coordinate: " + this.sections.get(key)); -+ } -+ } -+ region.deadSections.clear(); -+ } -+ -+ // if we removed dead sections, we should check if the region can be split into smaller ones -+ // otherwise, the region remains alive -+ if (!removedDeadSections) { -+ // didn't remove dead sections, don't check for split -+ region.state = ThreadedRegion.STATE_READY; -+ if (!region.expectingMergeFrom.isEmpty() || !region.mergeIntoLater.isEmpty()) { -+ throw new IllegalStateException("Illegal state " + region); -+ } -+ return; -+ } -+ -+ // first, we need to build copy of coordinate->section map of all sections in recalculate -+ final Long2ReferenceOpenHashMap> recalculateSections = region.sectionByKey.clone(); -+ -+ if (recalculateSections.isEmpty()) { -+ // looks like the region's sections were all dead, and now there is no region at all -+ region.state = ThreadedRegion.STATE_DEAD; -+ region.onRemove(true); -+ return; -+ } -+ -+ // merge radius is max, since recalculateSections includes the dead or empty sections -+ final int mergeRadius = Math.max(this.regionSectionMergeRadius, this.emptySectionCreateRadius); -+ -+ final List>> newRegions = new ArrayList<>(); -+ while (!recalculateSections.isEmpty()) { -+ // select any section, then BFS around it to find all of its neighbours to form a region -+ // once no more neighbours are found, the region is complete -+ final List> currRegion = new ArrayList<>(); -+ final Iterator> firstIterator = recalculateSections.values().iterator(); -+ -+ currRegion.add(firstIterator.next()); -+ firstIterator.remove(); -+ search_loop: -+ for (int idx = 0; idx < currRegion.size(); ++idx) { -+ final ThreadedRegionSection curr = currRegion.get(idx); -+ final int centerX = curr.sectionX; -+ final int centerZ = curr.sectionZ; -+ -+ // find neighbours in radius -+ for (int dz = -mergeRadius; dz <= mergeRadius; ++dz) { -+ for (int dx = -mergeRadius; dx <= mergeRadius; ++dx) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ -+ final ThreadedRegionSection section = recalculateSections.remove(CoordinateUtils.getChunkKey(dx + centerX, dz + centerZ)); -+ if (section == null) { -+ continue; -+ } -+ -+ currRegion.add(section); -+ -+ if (recalculateSections.isEmpty()) { -+ // no point in searching further -+ break search_loop; -+ } -+ } -+ } -+ } -+ -+ newRegions.add(currRegion); -+ } -+ -+ // now we have split the regions into separate parts, we can split recalculate -+ -+ if (newRegions.size() == 1) { -+ // no need to split anything, we're done here -+ region.state = ThreadedRegion.STATE_READY; -+ if (!region.expectingMergeFrom.isEmpty() || !region.mergeIntoLater.isEmpty()) { -+ throw new IllegalStateException("Illegal state " + region); -+ } -+ return; -+ } -+ -+ final List> newRegionObjects = new ArrayList<>(newRegions.size()); -+ for (int i = 0, len = newRegions.size(); i < len; ++i) { -+ newRegionObjects.add(new ThreadedRegion<>(this)); -+ } -+ -+ this.callbacks.preSplit(region, newRegionObjects); -+ -+ // need to split the region, so we need to kill the old one first -+ region.state = ThreadedRegion.STATE_DEAD; -+ region.onRemove(true); -+ -+ // create new regions -+ final Long2ReferenceOpenHashMap> newRegionsMap = new Long2ReferenceOpenHashMap<>(); -+ final ReferenceOpenHashSet> newRegionsSet = new ReferenceOpenHashSet<>(newRegionObjects); -+ -+ for (int i = 0, len = newRegions.size(); i < len; i++) { -+ final List> sections = newRegions.get(i); -+ final ThreadedRegion newRegion = newRegionObjects.get(i); -+ -+ for (final ThreadedRegionSection section : sections) { -+ section.setRegionRelease(null); -+ newRegion.addSection(section); -+ final ThreadedRegion curr = newRegionsMap.putIfAbsent(section.sectionKey, newRegion); -+ if (curr != null) { -+ throw new IllegalStateException("Expected no region at " + section + ", but got " + curr + ", should have put " + newRegion); -+ } -+ } -+ } -+ -+ region.split(newRegionsMap, newRegionsSet); -+ -+ // only after invoking data callbacks -+ -+ for (final ThreadedRegion newRegion : newRegionsSet) { -+ newRegion.state = ThreadedRegion.STATE_READY; -+ if (!newRegion.expectingMergeFrom.isEmpty() || !newRegion.mergeIntoLater.isEmpty()) { -+ throw new IllegalStateException("Illegal state " + newRegion); -+ } -+ newRegion.onCreate(); -+ this.callbacks.onRegionActive(newRegion); -+ } -+ } -+ -+ public static final class ThreadedRegion, S extends ThreadedRegionSectionData> { -+ -+ private static final AtomicLong REGION_ID_GENERATOR = new AtomicLong(); -+ -+ private static final int STATE_TRANSIENT = 0; -+ private static final int STATE_READY = 1; -+ private static final int STATE_TICKING = 2; -+ private static final int STATE_DEAD = 3; -+ -+ public final long id; -+ -+ private int state; -+ -+ private final Long2ReferenceOpenHashMap> sectionByKey = new Long2ReferenceOpenHashMap<>(); -+ private final ReferenceOpenHashSet> deadSections = new ReferenceOpenHashSet<>(); -+ -+ public final ThreadedRegionizer regioniser; -+ -+ private final R data; -+ -+ private final ReferenceOpenHashSet> mergeIntoLater = new ReferenceOpenHashSet<>(); -+ private final ReferenceOpenHashSet> expectingMergeFrom = new ReferenceOpenHashSet<>(); -+ -+ public ThreadedRegion(final ThreadedRegionizer regioniser) { -+ this.regioniser = regioniser; -+ this.id = REGION_ID_GENERATOR.getAndIncrement(); -+ this.state = STATE_TRANSIENT; -+ this.data = regioniser.callbacks.createNewData(this); -+ } -+ -+ public LongArrayList getOwnedSections() { -+ final boolean lock = this.regioniser.writeLockOwner != Thread.currentThread(); -+ if (lock) { -+ this.regioniser.regionLock.readLock(); -+ } -+ try { -+ final LongArrayList ret = new LongArrayList(this.sectionByKey.size()); -+ ret.addAll(this.sectionByKey.keySet()); -+ -+ return ret; -+ } finally { -+ if (lock) { -+ this.regioniser.regionLock.tryUnlockRead(); -+ } -+ } -+ } -+ -+ /** -+ * returns an iterator directly over the sections map. This is only to be used by a thread which is _ticking_ -+ * 'this' region. -+ */ -+ public LongIterator getOwnedSectionsUnsynchronised() { -+ return this.sectionByKey.keySet().iterator(); -+ } -+ -+ public LongArrayList getOwnedChunks() { -+ final boolean lock = this.regioniser.writeLockOwner != Thread.currentThread(); -+ if (lock) { -+ this.regioniser.regionLock.readLock(); -+ } -+ try { -+ final LongArrayList ret = new LongArrayList(); -+ for (final ThreadedRegionSection section : this.sectionByKey.values()) { -+ ret.addAll(section.getChunks()); -+ } -+ -+ return ret; -+ } finally { -+ if (lock) { -+ this.regioniser.regionLock.tryUnlockRead(); -+ } -+ } -+ } -+ -+ public Long getCenterSection() { -+ final LongArrayList sections = this.getOwnedSections(); -+ -+ final LongComparator comparator = (final long k1, final long k2) -> { -+ final int x1 = CoordinateUtils.getChunkX(k1); -+ final int x2 = CoordinateUtils.getChunkX(k2); -+ -+ final int z1 = CoordinateUtils.getChunkZ(x1); -+ final int z2 = CoordinateUtils.getChunkZ(x2); -+ -+ final int zCompare = Integer.compare(z1, z2); -+ if (zCompare != 0) { -+ return zCompare; -+ } -+ -+ return Integer.compare(x1, x2); -+ }; -+ -+ // note: regions don't always have a chunk section at this point, because the region may have been killed -+ if (sections.isEmpty()) { -+ return null; -+ } -+ -+ sections.sort(comparator); -+ -+ return Long.valueOf(sections.getLong(sections.size() >> 1)); -+ } -+ -+ public ChunkPos getCenterChunk() { -+ final LongArrayList chunks = this.getOwnedChunks(); -+ -+ final LongComparator comparator = (final long k1, final long k2) -> { -+ final int x1 = CoordinateUtils.getChunkX(k1); -+ final int x2 = CoordinateUtils.getChunkX(k2); -+ -+ final int z1 = CoordinateUtils.getChunkZ(k1); -+ final int z2 = CoordinateUtils.getChunkZ(k2); -+ -+ final int zCompare = Integer.compare(z1, z2); -+ if (zCompare != 0) { -+ return zCompare; -+ } -+ -+ return Integer.compare(x1, x2); -+ }; -+ chunks.sort(comparator); -+ -+ // note: regions don't always have a chunk at this point, because the region may have been killed -+ if (chunks.isEmpty()) { -+ return null; -+ } -+ -+ final long middle = chunks.getLong(chunks.size() >> 1); -+ -+ return new ChunkPos(CoordinateUtils.getChunkX(middle), CoordinateUtils.getChunkZ(middle)); -+ } -+ -+ private void onCreate() { -+ this.regioniser.onRegionCreate(this); -+ this.regioniser.callbacks.onRegionCreate(this); -+ } -+ -+ private void onRemove(final boolean wasActive) { -+ if (wasActive) { -+ this.regioniser.callbacks.onRegionInactive(this); -+ } -+ this.regioniser.callbacks.onRegionDestroy(this); -+ this.regioniser.onRegionDestroy(this); -+ } -+ -+ private final boolean hasNoAliveSections() { -+ return this.deadSections.size() == this.sectionByKey.size(); -+ } -+ -+ private final double getDeadSectionPercent() { -+ return (double)this.deadSections.size() / (double)this.sectionByKey.size(); -+ } -+ -+ private void split(final Long2ReferenceOpenHashMap> into, final ReferenceOpenHashSet> regions) { -+ if (this.data != null) { -+ this.data.split(this.regioniser, into, regions); -+ } -+ } -+ -+ boolean killAndMergeInto(final ThreadedRegion mergeTarget) { -+ if (this.state == STATE_TICKING) { -+ return false; -+ } -+ -+ this.regioniser.callbacks.preMerge(this, mergeTarget); -+ -+ this.tryKill(); -+ -+ this.mergeInto(mergeTarget); -+ -+ return true; -+ } -+ -+ private void mergeInto(final ThreadedRegion mergeTarget) { -+ if (this == mergeTarget) { -+ throw new IllegalStateException("Cannot merge a region onto itself"); -+ } -+ if (!this.isDead()) { -+ throw new IllegalStateException("Source region is not dead! Source " + this + ", target " + mergeTarget); -+ } else if (mergeTarget.isDead()) { -+ throw new IllegalStateException("Target region is dead! Source " + this + ", target " + mergeTarget); -+ } -+ -+ for (final ThreadedRegionSection section : this.sectionByKey.values()) { -+ section.setRegionRelease(null); -+ mergeTarget.addSection(section); -+ } -+ for (final ThreadedRegionSection deadSection : this.deadSections) { -+ if (this.sectionByKey.get(deadSection.sectionKey) != deadSection) { -+ throw new IllegalStateException("Source region does not even contain its own dead sections! Missing " + deadSection + " from region " + this); -+ } -+ if (!mergeTarget.deadSections.add(deadSection)) { -+ throw new IllegalStateException("Merge target contains dead section from source! Has " + deadSection + " from region " + this); -+ } -+ } -+ -+ // forward merge expectations -+ for (final ThreadedRegion region : this.expectingMergeFrom) { -+ if (!region.mergeIntoLater.remove(this)) { -+ throw new IllegalStateException("Region " + region + " was not supposed to merge into " + this + "?"); -+ } -+ if (region != mergeTarget) { -+ region.mergeIntoLater(mergeTarget); -+ } -+ } -+ -+ // forward merge into -+ for (final ThreadedRegion region : this.mergeIntoLater) { -+ if (!region.expectingMergeFrom.remove(this)) { -+ throw new IllegalStateException("Region " + this + " was not supposed to merge into " + region + "?"); -+ } -+ if (region != mergeTarget) { -+ mergeTarget.mergeIntoLater(region); -+ } -+ } -+ -+ // finally, merge data -+ if (this.data != null) { -+ this.data.mergeInto(mergeTarget); -+ } -+ } -+ -+ private void mergeIntoLater(final ThreadedRegion region) { -+ if (region.isDead()) { -+ throw new IllegalStateException("Trying to merge later into a dead region: " + region); -+ } -+ final boolean add1, add2; -+ if ((add1 = this.mergeIntoLater.add(region)) != (add2 = region.expectingMergeFrom.add(this))) { -+ throw new IllegalStateException("Inconsistent state between target merge " + region + " and this " + this + ": add1,add2:" + add1 + "," + add2); -+ } -+ } -+ -+ private boolean tryKill() { -+ switch (this.state) { -+ case STATE_TRANSIENT: { -+ this.state = STATE_DEAD; -+ this.onRemove(false); -+ return true; -+ } -+ case STATE_READY: { -+ this.state = STATE_DEAD; -+ this.onRemove(true); -+ return true; -+ } -+ case STATE_TICKING: { -+ return false; -+ } -+ case STATE_DEAD: { -+ throw new IllegalStateException("Already dead"); -+ } -+ default: { -+ throw new IllegalStateException("Unknown state: " + this.state); -+ } -+ } -+ } -+ -+ private boolean isDead() { -+ return this.state == STATE_DEAD; -+ } -+ -+ private boolean isTicking() { -+ return this.state == STATE_TICKING; -+ } -+ -+ private void removeDeadSection(final ThreadedRegionSection section) { -+ this.deadSections.remove(section); -+ } -+ -+ private void addDeadSection(final ThreadedRegionSection section) { -+ this.deadSections.add(section); -+ } -+ -+ private void addSection(final ThreadedRegionSection section) { -+ if (section.getRegionPlain() != null) { -+ throw new IllegalStateException("Section already has region"); -+ } -+ if (this.sectionByKey.putIfAbsent(section.sectionKey, section) != null) { -+ throw new IllegalStateException("Already have section " + section + ", mapped to " + this.sectionByKey.get(section.sectionKey)); -+ } -+ section.setRegionRelease(this); -+ } -+ -+ public R getData() { -+ return this.data; -+ } -+ -+ public boolean tryMarkTicking(final BooleanSupplier abort) { -+ this.regioniser.acquireWriteLock(); -+ try { -+ if (this.state != STATE_READY || abort.getAsBoolean()) { -+ return false; -+ } -+ -+ if (!this.mergeIntoLater.isEmpty() || !this.expectingMergeFrom.isEmpty()) { -+ throw new IllegalStateException("Region " + this + " should not be ready"); -+ } -+ -+ this.state = STATE_TICKING; -+ return true; -+ } finally { -+ this.regioniser.releaseWriteLock(); -+ } -+ } -+ -+ public boolean markNotTicking() { -+ this.regioniser.acquireWriteLock(); -+ try { -+ if (this.state != STATE_TICKING) { -+ throw new IllegalStateException("Attempting to release non-locked state"); -+ } -+ -+ this.regioniser.onRegionRelease(this); -+ -+ return this.state == STATE_READY; -+ } catch (final Throwable throwable) { -+ LOGGER.error("Failed to release region " + this, throwable); -+ SneakyThrow.sneaky(throwable); -+ return false; // unreachable -+ } finally { -+ this.regioniser.releaseWriteLock(); -+ } -+ } -+ -+ @Override -+ public String toString() { -+ final StringBuilder ret = new StringBuilder(128); -+ -+ ret.append("ThreadedRegion{"); -+ ret.append("state=").append(this.state).append(','); -+ // To avoid recursion in toString, maybe fix later? -+ //ret.append("mergeIntoLater=").append(this.mergeIntoLater).append(','); -+ //ret.append("expectingMergeFrom=").append(this.expectingMergeFrom).append(','); -+ -+ ret.append("sectionCount=").append(this.sectionByKey.size()).append(','); -+ ret.append("sections=["); -+ for (final Iterator> iterator = this.sectionByKey.values().iterator(); iterator.hasNext();) { -+ final ThreadedRegionSection section = iterator.next(); -+ -+ ret.append(section.toString()); -+ if (iterator.hasNext()) { -+ ret.append(','); -+ } -+ } -+ ret.append(']'); -+ -+ ret.append('}'); -+ return ret.toString(); -+ } -+ } -+ -+ public static final class ThreadedRegionSection, S extends ThreadedRegionSectionData> { -+ -+ public final int sectionX; -+ public final int sectionZ; -+ public final long sectionKey; -+ private final long[] chunksBitset; -+ private int chunkCount; -+ private int nonEmptyNeighbours; -+ -+ private ThreadedRegion region; -+ private static final VarHandle REGION_HANDLE = ConcurrentUtil.getVarHandle(ThreadedRegionSection.class, "region", ThreadedRegion.class); -+ -+ public final ThreadedRegionizer regioniser; -+ -+ private final int regionChunkShift; -+ private final int regionChunkMask; -+ -+ private final S data; -+ -+ private ThreadedRegion getRegionPlain() { -+ return (ThreadedRegion)REGION_HANDLE.get(this); -+ } -+ -+ private ThreadedRegion getRegionAcquire() { -+ return (ThreadedRegion)REGION_HANDLE.getAcquire(this); -+ } -+ -+ private void setRegionRelease(final ThreadedRegion value) { -+ REGION_HANDLE.setRelease(this, value); -+ } -+ -+ // creates an empty section with zero non-empty neighbours -+ private ThreadedRegionSection(final int sectionX, final int sectionZ, final ThreadedRegionizer regioniser) { -+ this.sectionX = sectionX; -+ this.sectionZ = sectionZ; -+ this.sectionKey = CoordinateUtils.getChunkKey(sectionX, sectionZ); -+ this.chunksBitset = new long[Math.max(1, regioniser.regionSectionChunkSize * regioniser.regionSectionChunkSize / Long.SIZE)]; -+ this.regioniser = regioniser; -+ this.regionChunkShift = regioniser.sectionChunkShift; -+ this.regionChunkMask = regioniser.regionSectionChunkSize - 1; -+ this.data = regioniser.callbacks -+ .createNewSectionData(sectionX, sectionZ, this.regionChunkShift); -+ } -+ -+ // creates a section with an initial chunk with zero non-empty neighbours -+ private ThreadedRegionSection(final int sectionX, final int sectionZ, final ThreadedRegionizer regioniser, -+ final int chunkXInit, final int chunkZInit) { -+ this(sectionX, sectionZ, regioniser); -+ -+ final int initIndex = this.getChunkIndex(chunkXInit, chunkZInit); -+ this.chunkCount = 1; -+ this.chunksBitset[initIndex >>> 6] = 1L << (initIndex & (Long.SIZE - 1)); // index / Long.SIZE -+ } -+ -+ // creates an empty section with the specified number of non-empty neighbours -+ private ThreadedRegionSection(final int sectionX, final int sectionZ, final ThreadedRegionizer regioniser, -+ final int nonEmptyNeighbours) { -+ this(sectionX, sectionZ, regioniser); -+ -+ this.nonEmptyNeighbours = nonEmptyNeighbours; -+ } -+ -+ public LongArrayList getChunks() { -+ final LongArrayList ret = new LongArrayList(); -+ -+ if (this.chunkCount == 0) { -+ return ret; -+ } -+ -+ final int shift = this.regionChunkShift; -+ final int mask = this.regionChunkMask; -+ final int offsetX = this.sectionX << shift; -+ final int offsetZ = this.sectionZ << shift; -+ -+ final long[] bitset = this.chunksBitset; -+ for (int arrIdx = 0, arrLen = bitset.length; arrIdx < arrLen; ++arrIdx) { -+ long value = bitset[arrIdx]; -+ -+ for (int i = 0, bits = Long.bitCount(value); i < bits; ++i) { -+ final int valueIdx = Long.numberOfTrailingZeros(value); -+ value ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(value); -+ -+ final int idx = valueIdx | (arrIdx << 6); -+ -+ final int localX = idx & mask; -+ final int localZ = (idx >>> shift) & mask; -+ -+ ret.add(CoordinateUtils.getChunkKey(localX | offsetX, localZ | offsetZ)); -+ } -+ } -+ -+ return ret; -+ } -+ -+ private boolean isEmpty() { -+ return this.chunkCount == 0; -+ } -+ -+ private boolean hasOnlyOneChunk() { -+ return this.chunkCount == 1; -+ } -+ -+ public boolean hasNonEmptyNeighbours() { -+ return this.nonEmptyNeighbours != 0; -+ } -+ -+ /** -+ * Returns the section data associated with this region section. May be {@code null}. -+ */ -+ public S getData() { -+ return this.data; -+ } -+ -+ /** -+ * Returns the region that owns this section. Unsynchronised access may produce outdateed or transient results. -+ */ -+ public ThreadedRegion getRegion() { -+ return this.getRegionAcquire(); -+ } -+ -+ private int getChunkIndex(final int chunkX, final int chunkZ) { -+ return (chunkX & this.regionChunkMask) | ((chunkZ & this.regionChunkMask) << this.regionChunkShift); -+ } -+ -+ private void markAlive() { -+ this.getRegionPlain().removeDeadSection(this); -+ } -+ -+ private void markDead() { -+ this.getRegionPlain().addDeadSection(this); -+ } -+ -+ private void incrementNonEmptyNeighbours() { -+ if (++this.nonEmptyNeighbours == 1 && this.chunkCount == 0) { -+ this.markAlive(); -+ } -+ final int createRadius = this.regioniser.emptySectionCreateRadius; -+ if (this.nonEmptyNeighbours >= ((createRadius * 2 + 1) * (createRadius * 2 + 1))) { -+ throw new IllegalStateException("Non empty neighbours exceeded max value for radius " + createRadius); -+ } -+ } -+ -+ private void decrementNonEmptyNeighbours() { -+ if (--this.nonEmptyNeighbours == 0 && this.chunkCount == 0) { -+ this.markDead(); -+ } -+ if (this.nonEmptyNeighbours < 0) { -+ throw new IllegalStateException("Non empty neighbours reached zero"); -+ } -+ } -+ -+ /** -+ * Returns whether the chunk was zero. Effectively returns whether the caller needs to create -+ * dead sections / increase non-empty neighbour count for neighbouring sections. -+ */ -+ private boolean addChunk(final int chunkX, final int chunkZ) { -+ final int index = this.getChunkIndex(chunkX, chunkZ); -+ final long bitset = this.chunksBitset[index >>> 6]; // index / Long.SIZE -+ final long after = this.chunksBitset[index >>> 6] = bitset | (1L << (index & (Long.SIZE - 1))); -+ if (after == bitset) { -+ throw new IllegalStateException("Cannot add a chunk to a section which already has the chunk! RegionSection: " + this + ", global chunk: " + new ChunkPos(chunkX, chunkZ).toString()); -+ } -+ final boolean notEmpty = ++this.chunkCount == 1; -+ if (notEmpty && this.nonEmptyNeighbours == 0) { -+ this.markAlive(); -+ } -+ return notEmpty; -+ } -+ -+ /** -+ * Returns whether the chunk count is now zero. Effectively returns whether -+ * the caller needs to decrement the neighbour count for neighbouring sections. -+ */ -+ private boolean removeChunk(final int chunkX, final int chunkZ) { -+ final int index = this.getChunkIndex(chunkX, chunkZ); -+ final long before = this.chunksBitset[index >>> 6]; // index / Long.SIZE -+ final long bitset = this.chunksBitset[index >>> 6] = before & ~(1L << (index & (Long.SIZE - 1))); -+ if (before == bitset) { -+ throw new IllegalStateException("Cannot remove a chunk from a section which does not have that chunk! RegionSection: " + this + ", global chunk: " + new ChunkPos(chunkX, chunkZ).toString()); -+ } -+ final boolean empty = --this.chunkCount == 0; -+ if (empty && this.nonEmptyNeighbours == 0) { -+ this.markDead(); -+ } -+ return empty; -+ } -+ -+ @Override -+ public String toString() { -+ return "RegionSection{" + -+ "sectionCoordinate=" + new ChunkPos(this.sectionX, this.sectionZ).toString() + "," + -+ "chunkCount=" + this.chunkCount + "," + -+ "chunksBitset=" + toString(this.chunksBitset) + "," + -+ "nonEmptyNeighbours=" + this.nonEmptyNeighbours + "," + -+ "hash=" + this.hashCode() + -+ "}"; -+ } -+ -+ public String toStringWithRegion() { -+ return "RegionSection{" + -+ "sectionCoordinate=" + new ChunkPos(this.sectionX, this.sectionZ).toString() + "," + -+ "chunkCount=" + this.chunkCount + "," + -+ "chunksBitset=" + toString(this.chunksBitset) + "," + -+ "hash=" + this.hashCode() + "," + -+ "nonEmptyNeighbours=" + this.nonEmptyNeighbours + "," + -+ "region=" + this.getRegionAcquire() + -+ "}"; -+ } -+ -+ private static String toString(final long[] array) { -+ final StringBuilder ret = new StringBuilder(); -+ final char[] zeros = new char[Long.SIZE / 4]; -+ for (final long value : array) { -+ // zero pad the hex string -+ Arrays.fill(zeros, '0'); -+ final String string = Long.toHexString(value); -+ System.arraycopy(string.toCharArray(), 0, zeros, zeros.length - string.length(), string.length()); -+ -+ ret.append(zeros); -+ } -+ -+ return ret.toString(); -+ } -+ } -+ -+ public static interface ThreadedRegionData, S extends ThreadedRegionSectionData> { -+ -+ /** -+ * Splits this region data into the specified regions set. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param regioniser Regioniser for which the regions reside in. -+ * @param into A map of region section coordinate key to the region that owns the section. -+ * @param regions The set of regions to split into. -+ */ -+ public void split(final ThreadedRegionizer regioniser, final Long2ReferenceOpenHashMap> into, -+ final ReferenceOpenHashSet> regions); -+ -+ /** -+ * Callback to merge {@code this} region data into the specified region. The state of the region is undefined -+ * except that its region data is already created. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param into Specified region. -+ */ -+ public void mergeInto(final ThreadedRegion into); -+ } -+ -+ public static interface ThreadedRegionSectionData {} -+ -+ public static interface RegionCallbacks, S extends ThreadedRegionSectionData> { -+ -+ /** -+ * Creates new section data for the specified section x and section z. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param sectionX x coordinate of the section. -+ * @param sectionZ z coordinate of the section. -+ * @param sectionShift The signed right shift value that can be applied to any chunk coordinate that -+ * produces a section coordinate. -+ * @return New section data, may be {@code null}. -+ */ -+ public S createNewSectionData(final int sectionX, final int sectionZ, final int sectionShift); -+ -+ /** -+ * Creates new region data for the specified region. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param forRegion The region to create the data for. -+ * @return New region data, may be {@code null}. -+ */ -+ public R createNewData(final ThreadedRegion forRegion); -+ -+ /** -+ * Callback for when a region is created. This is invoked after the region is completely set up, -+ * so its data and owned sections are reliable to inspect. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param region The region that was created. -+ */ -+ public void onRegionCreate(final ThreadedRegion region); -+ -+ /** -+ * Callback for when a region is destroyed. This is invoked before the region is actually destroyed; so -+ * its data and owned sections are reliable to inspect. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param region The region that is about to be destroyed. -+ */ -+ public void onRegionDestroy(final ThreadedRegion region); -+ -+ /** -+ * Callback for when a region is considered "active." An active region x is a non-destroyed region which -+ * is not scheduled to merge into another region y and there are no non-destroyed regions z which are -+ * scheduled to merge into the region x. Equivalently, an active region is not directly adjacent to any -+ * other region considering the regioniser's empty section radius. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param region The region that is now active. -+ */ -+ public void onRegionActive(final ThreadedRegion region); -+ -+ /** -+ * Callback for when a region transistions becomes inactive. An inactive region is non-destroyed, but -+ * has neighbouring adjacent regions considering the regioniser's empty section radius. Effectively, -+ * an inactive region may not tick and needs to be merged into its neighbouring regions. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param region The region that is now inactive. -+ */ -+ public void onRegionInactive(final ThreadedRegion region); -+ -+ /** -+ * Callback for when a region (from) is about to be merged into a target region (into). Note that -+ * {@code from} is still alive and is a distinct region. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param from The region that will be merged into the target. -+ * @param into The target of the merge. -+ */ -+ public void preMerge(final ThreadedRegion from, final ThreadedRegion into); -+ -+ /** -+ * Callback for when a region (from) is about to be split into a list of target region (into). Note that -+ * {@code from} is still alive, while the list of target regions are not initialised. -+ *

    -+ * Note: -+ *

    -+ *

    -+ * This function is always called while holding critical locks and as such should not attempt to block on anything, and -+ * should NOT retrieve or modify ANY world state. -+ *

    -+ * @param from The region that will be merged into the target. -+ * @param into The list of regions to split into. -+ */ -+ public void preSplit(final ThreadedRegion from, final List> into); -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickData.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickData.java.patch deleted file mode 100644 index 2daab49..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickData.java.patch +++ /dev/null @@ -1,336 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/TickData.java -@@ -1,0 +_,333 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.util.TimeUtil; -+import io.papermc.paper.util.IntervalledCounter; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+ -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+ -+public final class TickData { -+ -+ private final long interval; // ns -+ -+ private final ArrayDeque 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 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 collapsedData = new ArrayList<>(); -+ for (int i = 0, len = allData.size(); i < len; ++i) { -+ final List 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 -+ ) {} -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickRegionScheduler.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickRegionScheduler.java.patch deleted file mode 100644 index 8d1f3f9..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickRegionScheduler.java.patch +++ /dev/null @@ -1,567 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java -@@ -1,0 +_,564 @@ -+package io.papermc.paper.threadedregions; -+ -+import ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool; -+import ca.spottedleaf.concurrentutil.util.TimeUtil; -+import ca.spottedleaf.moonrise.common.util.TickThread; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.util.TraceUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import org.slf4j.Logger; -+import java.lang.management.ManagementFactory; -+import java.lang.management.ThreadMXBean; -+import java.util.concurrent.ThreadFactory; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.BooleanSupplier; -+ -+public final class TickRegionScheduler { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean(); -+ private static final boolean MEASURE_CPU_TIME; -+ static { -+ MEASURE_CPU_TIME = THREAD_MX_BEAN.isThreadCpuTimeSupported(); -+ if (MEASURE_CPU_TIME) { -+ THREAD_MX_BEAN.setThreadCpuTimeEnabled(true); -+ } else { -+ LOGGER.warn("TickRegionScheduler CPU time measurement is not available"); -+ } -+ } -+ -+ public static final int TICK_RATE = 20; -+ public static final long TIME_BETWEEN_TICKS = 1_000_000_000L / TICK_RATE; // ns -+ -+ private final SchedulerThreadPool scheduler; -+ -+ public TickRegionScheduler(final int threads) { -+ this.scheduler = new SchedulerThreadPool(threads, new ThreadFactory() { -+ private final AtomicInteger idGenerator = new AtomicInteger(); -+ -+ @Override -+ public Thread newThread(final Runnable run) { -+ final Thread ret = new TickThreadRunner(run, "Region Scheduler Thread #" + this.idGenerator.getAndIncrement()); -+ ret.setUncaughtExceptionHandler(TickRegionScheduler.this::uncaughtException); -+ return ret; -+ } -+ }); -+ } -+ -+ public int getTotalThreadCount() { -+ return this.scheduler.getThreads().length; -+ } -+ -+ private static void setTickingRegion(final ThreadedRegionizer.ThreadedRegion 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 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 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). -+ */ -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickRegions.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickRegions.java.patch deleted file mode 100644 index a68ae79..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/TickRegions.java.patch +++ /dev/null @@ -1,416 +0,0 @@ ---- a/io/papermc/paper/threadedregions/TickRegions.java -+++ b/io/papermc/paper/threadedregions/TickRegions.java -@@ -1,10 +_,410 @@ - package io.papermc.paper.threadedregions; - --// placeholder class for Folia --public class TickRegions { -+import ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool; -+import ca.spottedleaf.concurrentutil.util.TimeUtil; -+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; -+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import org.slf4j.Logger; -+import java.util.Iterator; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.BooleanSupplier; -+ -+public final class TickRegions implements ThreadedRegionizer.RegionCallbacks { -+ -+ 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 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 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 region) { -+ // nothing for now -+ } -+ -+ @Override -+ public void onRegionActive(final ThreadedRegionizer.ThreadedRegion region) { -+ final TickRegionData data = region.getData(); -+ -+ data.tickHandle.checkInitialSchedule(); -+ scheduler.scheduleRegion(data.tickHandle); -+ } -+ -+ @Override -+ public void onRegionInactive(final ThreadedRegionizer.ThreadedRegion 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 from, -+ final ThreadedRegionizer.ThreadedRegion into) { -+ -+ } -+ -+ @Override -+ public void preSplit(final ThreadedRegionizer.ThreadedRegion from, -+ final java.util.List> 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 { -+ -+ 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 region; -+ public final ServerLevel world; -+ -+ // generic regionised data -+ private final Reference2ReferenceOpenHashMap, 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 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 getRegionizedData(final RegionizedData regionizedData) { -+ return (T)this.regionizedData.get(regionizedData); -+ } -+ -+ T getOrCreateRegionizedData(final RegionizedData 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 regioniser, -+ final Long2ReferenceOpenHashMap> into, -+ final ReferenceOpenHashSet> 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 region : regions) { -+ final TickRegionData data = region.getData(); -+ data.tickHandle.copyDeadlineAndTickCount(this.tickHandle); -+ } -+ -+ // generic regionised data -+ for (final Iterator, Object>> dataIterator = this.regionizedData.reference2ReferenceEntrySet().fastIterator(); -+ dataIterator.hasNext();) { -+ final Reference2ReferenceMap.Entry, Object> regionDataEntry = dataIterator.next(); -+ final RegionizedData data = regionDataEntry.getKey(); -+ final Object from = regionDataEntry.getValue(); -+ -+ final ReferenceOpenHashSet dataSet = new ReferenceOpenHashSet<>(regions.size(), 0.75f); -+ -+ for (final ThreadedRegionizer.ThreadedRegion region : regions) { -+ dataSet.add(region.getData().getOrCreateRegionizedData(data)); -+ } -+ -+ final Long2ReferenceOpenHashMap regionToData = new Long2ReferenceOpenHashMap<>(into.size(), 0.75f); -+ -+ for (final Iterator>> regionIterator = into.long2ReferenceEntrySet().fastIterator(); -+ regionIterator.hasNext();) { -+ final Long2ReferenceMap.Entry> entry = regionIterator.next(); -+ final ThreadedRegionizer.ThreadedRegion region = entry.getValue(); -+ final Object to = region.getData().getOrCreateRegionizedData(data); -+ -+ regionToData.put(entry.getLongKey(), to); -+ } -+ -+ ((RegionizedData)data).getCallback().split(from, shift, regionToData, dataSet); -+ } -+ -+ // chunk holder manager data -+ { -+ final ReferenceOpenHashSet dataSet = new ReferenceOpenHashSet<>(regions.size(), 0.75f); -+ -+ for (final ThreadedRegionizer.ThreadedRegion region : regions) { -+ dataSet.add(region.getData().holderManagerRegionData); -+ } -+ -+ final Long2ReferenceOpenHashMap regionToData = new Long2ReferenceOpenHashMap<>(into.size(), 0.75f); -+ -+ for (final Iterator>> regionIterator = into.long2ReferenceEntrySet().fastIterator(); -+ regionIterator.hasNext();) { -+ final Long2ReferenceMap.Entry> entry = regionIterator.next(); -+ final ThreadedRegionizer.ThreadedRegion 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 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, Object>> iterator = this.regionizedData.reference2ReferenceEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Reference2ReferenceMap.Entry, Object> entry = iterator.next(); -+ final RegionizedData regionizedData = entry.getKey(); -+ final Object from = entry.getValue(); -+ final Object to = into.getData().getOrCreateRegionizedData(regionizedData); -+ -+ ((RegionizedData)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(); -+ } - } - - } diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/commands/CommandServerHealth.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/commands/CommandServerHealth.java.patch deleted file mode 100644 index 6600144..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/commands/CommandServerHealth.java.patch +++ /dev/null @@ -1,358 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/commands/CommandServerHealth.java -@@ -1,0 +_,355 @@ -+package io.papermc.paper.threadedregions.commands; -+ -+import io.papermc.paper.threadedregions.RegionizedServer; -+import io.papermc.paper.threadedregions.RegionizedWorldData; -+import io.papermc.paper.threadedregions.ThreadedRegionizer; -+import io.papermc.paper.threadedregions.TickData; -+import io.papermc.paper.threadedregions.TickRegionScheduler; -+import io.papermc.paper.threadedregions.TickRegions; -+import it.unimi.dsi.fastutil.doubles.DoubleArrayList; -+import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.TextComponent; -+import net.kyori.adventure.text.event.ClickEvent; -+import net.kyori.adventure.text.event.HoverEvent; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextColor; -+import net.kyori.adventure.text.format.TextDecoration; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import org.bukkit.Bukkit; -+import org.bukkit.World; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.entity.Entity; -+import org.bukkit.entity.Player; -+import java.text.DecimalFormat; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+import java.util.Locale; -+ -+public final class CommandServerHealth extends Command { -+ -+ private static final ThreadLocal TWO_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { -+ return new DecimalFormat("#,##0.00"); -+ }); -+ private static final ThreadLocal ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { -+ return new DecimalFormat("#,##0.0"); -+ }); -+ private static final ThreadLocal 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("/ [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 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> 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 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 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, 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, TickData.TickReportData> -+ pair = regionsBelowThreshold.get(i); -+ -+ final TickData.TickReportData report = pair.right(); -+ final ThreadedRegionizer.ThreadedRegion 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 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<>(); -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/commands/CommandUtil.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/commands/CommandUtil.java.patch deleted file mode 100644 index 2b3ac5f..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/commands/CommandUtil.java.patch +++ /dev/null @@ -1,124 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/commands/CommandUtil.java -@@ -1,0 +_,121 @@ -+package io.papermc.paper.threadedregions.commands; -+ -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextColor; -+import net.kyori.adventure.util.HSVLike; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Function; -+ -+public final class CommandUtil { -+ -+ public static List getSortedList(final Iterable iterable) { -+ final List ret = new ArrayList<>(); -+ for (final String val : iterable) { -+ ret.add(val); -+ } -+ -+ ret.sort(String.CASE_INSENSITIVE_ORDER); -+ -+ return ret; -+ } -+ -+ public static List getSortedList(final Iterable iterable, final String prefix) { -+ final List 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 List getSortedList(final Iterable iterable, final Function transform) { -+ final List 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 List getSortedList(final Iterable iterable, final Function transform, final String prefix) { -+ final List 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() {} -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java.patch deleted file mode 100644 index acfcb6c..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java.patch +++ /dev/null @@ -1,427 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java -@@ -1,0 +_,424 @@ -+package io.papermc.paper.threadedregions.scheduler; -+ -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import ca.spottedleaf.concurrentutil.util.Validate; -+import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -+import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; -+import io.papermc.paper.threadedregions.RegionizedData; -+import io.papermc.paper.threadedregions.RegionizedServer; -+import io.papermc.paper.threadedregions.TickRegionScheduler; -+import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.util.Unit; -+import org.bukkit.Bukkit; -+import org.bukkit.World; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.plugin.IllegalPluginAccessException; -+import org.bukkit.plugin.Plugin; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayList; -+import java.util.Iterator; -+import java.util.List; -+import java.util.function.Consumer; -+import java.util.logging.Level; -+ -+public final class FoliaRegionScheduler implements RegionScheduler { -+ -+ private static Runnable wrap(final Plugin plugin, final World world, final int chunkX, final int chunkZ, final Runnable run) { -+ return () -> { -+ try { -+ run.run(); -+ } catch (final Throwable throwable) { -+ plugin.getLogger().log(Level.WARNING, "Location task for " + plugin.getDescription().getFullName() -+ + " in world " + world + " at " + chunkX + ", " + chunkZ + " generated an exception", throwable); -+ } -+ }; -+ } -+ -+ private static final RegionizedData SCHEDULER_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 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 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 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 REGIONISER_CALLBACK = new RegionizedData.RegioniserCallback<>() { -+ @Override -+ public void merge(final Scheduler from, final Scheduler into, final long fromTickOffset) { -+ for (final Iterator>>> sectionIterator = from.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator(); -+ sectionIterator.hasNext();) { -+ final Long2ObjectMap.Entry>> entry = sectionIterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final Long2ObjectOpenHashMap> section = entry.getValue(); -+ -+ final Long2ObjectOpenHashMap> sectionAdjusted = new Long2ObjectOpenHashMap<>(section.size()); -+ -+ for (final Iterator>> iterator = section.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry> e = iterator.next(); -+ final long newTick = e.getLongKey() + fromTickOffset; -+ final List tasks = e.getValue(); -+ -+ sectionAdjusted.put(newTick, tasks); -+ } -+ -+ into.tasksByDeadlineBySection.put(sectionKey, sectionAdjusted); -+ } -+ } -+ -+ @Override -+ public void split(final Scheduler from, final int chunkToRegionShift, final Long2ReferenceOpenHashMap regionToData, -+ final ReferenceOpenHashSet dataSet) { -+ for (final Scheduler into : dataSet) { -+ into.tickCount = from.tickCount; -+ } -+ -+ for (final Iterator>>> sectionIterator = from.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator(); -+ sectionIterator.hasNext();) { -+ final Long2ObjectMap.Entry>> entry = sectionIterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final Long2ObjectOpenHashMap> 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>> 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> 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 run = new ArrayList<>(); -+ -+ for (final Iterator>>> sectionIterator = this.tasksByDeadlineBySection.long2ObjectEntrySet().fastIterator(); -+ sectionIterator.hasNext();) { -+ final Long2ObjectMap.Entry>> entry = sectionIterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final Long2ObjectOpenHashMap> section = entry.getValue(); -+ -+ final List 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 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 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); -+ } -+ } -+ } -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java.patch deleted file mode 100644 index e813649..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java.patch +++ /dev/null @@ -1,82 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/util/SimpleThreadLocalRandomSource.java -@@ -1,0 +_,79 @@ -+package io.papermc.paper.threadedregions.util; -+ -+import net.minecraft.util.RandomSource; -+import net.minecraft.world.level.levelgen.BitRandomSource; -+import net.minecraft.world.level.levelgen.PositionalRandomFactory; -+import java.util.concurrent.ThreadLocalRandom; -+ -+public final class SimpleThreadLocalRandomSource implements BitRandomSource { -+ -+ public static final SimpleThreadLocalRandomSource INSTANCE = new SimpleThreadLocalRandomSource(); -+ -+ private final PositionalRandomFactory positionalRandomFactory = new SimpleThreadLocalRandomSource.SimpleThreadLocalRandomPositionalRandomFactory(); -+ -+ private SimpleThreadLocalRandomSource() {} -+ -+ @Override -+ public int next(final int bits) { -+ return ThreadLocalRandom.current().nextInt() >>> (Integer.SIZE - bits); -+ } -+ -+ @Override -+ public int nextInt() { -+ return ThreadLocalRandom.current().nextInt(); -+ } -+ -+ @Override -+ public int nextInt(final int bound) { -+ if (bound <= 0) { -+ throw new IllegalArgumentException(); -+ } -+ -+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ -+ final long value = (long)this.nextInt() & 0xFFFFFFFFL; -+ return (int)((value * (long)bound) >>> Integer.SIZE); -+ } -+ -+ @Override -+ public void setSeed(final long seed) { -+ // no-op -+ } -+ -+ @Override -+ public double nextGaussian() { -+ return ThreadLocalRandom.current().nextGaussian(); -+ } -+ -+ @Override -+ public RandomSource fork() { -+ return this; -+ } -+ -+ @Override -+ public PositionalRandomFactory forkPositional() { -+ return this.positionalRandomFactory; -+ } -+ -+ private static final class SimpleThreadLocalRandomPositionalRandomFactory implements PositionalRandomFactory { -+ -+ @Override -+ public RandomSource fromHashOf(final String seed) { -+ return SimpleThreadLocalRandomSource.INSTANCE; -+ } -+ -+ @Override -+ public RandomSource fromSeed(final long seed) { -+ return SimpleThreadLocalRandomSource.INSTANCE; -+ } -+ -+ @Override -+ public RandomSource at(final int x, final int y, final int z) { -+ return SimpleThreadLocalRandomSource.INSTANCE; -+ } -+ -+ @Override -+ public void parityConfigString(final StringBuilder info) { -+ info.append("SimpleThreadLocalRandomPositionalRandomFactory{}"); -+ } -+ } -+} diff --git a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java.patch b/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java.patch deleted file mode 100644 index 2c5a7fd..0000000 --- a/folia-server/minecraft-patches/sources/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java.patch +++ /dev/null @@ -1,76 +0,0 @@ ---- /dev/null -+++ b/io/papermc/paper/threadedregions/util/ThreadLocalRandomSource.java -@@ -1,0 +_,73 @@ -+package io.papermc.paper.threadedregions.util; -+ -+import net.minecraft.util.RandomSource; -+import net.minecraft.world.level.levelgen.BitRandomSource; -+import net.minecraft.world.level.levelgen.PositionalRandomFactory; -+import java.util.concurrent.ThreadLocalRandom; -+ -+public final class ThreadLocalRandomSource implements BitRandomSource { -+ -+ public static final ThreadLocalRandomSource INSTANCE = new ThreadLocalRandomSource(); -+ -+ private final PositionalRandomFactory positionalRandomFactory = new ThreadLocalRandomPositionalRandomFactory(); -+ -+ private ThreadLocalRandomSource() {} -+ -+ @Override -+ public int next(final int bits) { -+ return ThreadLocalRandom.current().nextInt() >>> (Integer.SIZE - bits); -+ } -+ -+ @Override -+ public int nextInt() { -+ return ThreadLocalRandom.current().nextInt(); -+ } -+ -+ @Override -+ public int nextInt(final int bound) { -+ return ThreadLocalRandom.current().nextInt(bound); -+ } -+ -+ @Override -+ public void setSeed(final long seed) { -+ // no-op -+ } -+ -+ @Override -+ public double nextGaussian() { -+ return ThreadLocalRandom.current().nextGaussian(); -+ } -+ -+ @Override -+ public RandomSource fork() { -+ return this; -+ } -+ -+ @Override -+ public PositionalRandomFactory forkPositional() { -+ return this.positionalRandomFactory; -+ } -+ -+ private static final class ThreadLocalRandomPositionalRandomFactory implements PositionalRandomFactory { -+ -+ @Override -+ public RandomSource fromHashOf(final String seed) { -+ return ThreadLocalRandomSource.INSTANCE; -+ } -+ -+ @Override -+ public RandomSource fromSeed(final long seed) { -+ return ThreadLocalRandomSource.INSTANCE; -+ } -+ -+ @Override -+ public RandomSource at(final int x, final int y, final int z) { -+ return ThreadLocalRandomSource.INSTANCE; -+ } -+ -+ @Override -+ public void parityConfigString(final StringBuilder info) { -+ info.append("ThreadLocalRandomPositionalRandomFactory{}"); -+ } -+ } -+} diff --git a/folia-server/minecraft-patches/sources/net/minecraft/commands/CommandSourceStack.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/commands/CommandSourceStack.java.patch deleted file mode 100644 index ccc2983..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/commands/CommandSourceStack.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/commands/CommandSourceStack.java -+++ b/net/minecraft/commands/CommandSourceStack.java -@@ -91,7 +_,7 @@ - CommandResultCallback.EMPTY, - EntityAnchorArgument.Anchor.FEET, - CommandSigningContext.ANONYMOUS, -- TaskChainer.immediate(server) -+ TaskChainer.immediate((Runnable run) -> { io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(run);}) // Folia - region threading - ); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/commands/Commands.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/commands/Commands.java.patch deleted file mode 100644 index 7c49aba..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/commands/Commands.java.patch +++ /dev/null @@ -1,112 +0,0 @@ ---- a/net/minecraft/commands/Commands.java -+++ b/net/minecraft/commands/Commands.java -@@ -153,13 +_,13 @@ - AdvancementCommands.register(this.dispatcher); - AttributeCommand.register(this.dispatcher, context); - ExecuteCommand.register(this.dispatcher, context); -- BossBarCommands.register(this.dispatcher, context); -+ //BossBarCommands.register(this.dispatcher, context); // Folia - region threading - TODO - ClearInventoryCommands.register(this.dispatcher, context); -- CloneCommands.register(this.dispatcher, context); -+ //CloneCommands.register(this.dispatcher, context); // Folia - region threading - TODO - DamageCommand.register(this.dispatcher, context); -- DataCommands.register(this.dispatcher); -- DataPackCommand.register(this.dispatcher); -- DebugCommand.register(this.dispatcher); -+ //DataCommands.register(this.dispatcher); // Folia - region threading - TODO -+ //DataPackCommand.register(this.dispatcher); // Folia - region threading - TODO -+ //DebugCommand.register(this.dispatcher); // Folia - region threading - TODO - DefaultGameModeCommands.register(this.dispatcher); - DifficultyCommand.register(this.dispatcher); - EffectCommands.register(this.dispatcher, context); -@@ -169,47 +_,47 @@ - FillCommand.register(this.dispatcher, context); - FillBiomeCommand.register(this.dispatcher, context); - ForceLoadCommand.register(this.dispatcher); -- FunctionCommand.register(this.dispatcher); -+ //FunctionCommand.register(this.dispatcher); // Folia - region threading - TODO - GameModeCommand.register(this.dispatcher); - GameRuleCommand.register(this.dispatcher, context); - GiveCommand.register(this.dispatcher, context); - HelpCommand.register(this.dispatcher); -- ItemCommands.register(this.dispatcher, context); -+ //ItemCommands.register(this.dispatcher, context); // Folia - region threading - TODO later - KickCommand.register(this.dispatcher); - KillCommand.register(this.dispatcher); - ListPlayersCommand.register(this.dispatcher); - LocateCommand.register(this.dispatcher, context); -- LootCommand.register(this.dispatcher, context); -+ //LootCommand.register(this.dispatcher, context); // Folia - region threading - TODO later - MsgCommand.register(this.dispatcher); - ParticleCommand.register(this.dispatcher, context); - PlaceCommand.register(this.dispatcher); - PlaySoundCommand.register(this.dispatcher); - RandomCommand.register(this.dispatcher); -- ReloadCommand.register(this.dispatcher); -+ //ReloadCommand.register(this.dispatcher); // Folia - region threading - RecipeCommand.register(this.dispatcher); -- ReturnCommand.register(this.dispatcher); -- RideCommand.register(this.dispatcher); -- RotateCommand.register(this.dispatcher); -+ //ReturnCommand.register(this.dispatcher); // Folia - region threading - TODO later -+ //RideCommand.register(this.dispatcher); // Folia - region threading - TODO later -+ //RotateCommand.register(this.dispatcher); // Folia - region threading - TODO later - SayCommand.register(this.dispatcher); -- ScheduleCommand.register(this.dispatcher); -- ScoreboardCommand.register(this.dispatcher, context); -+ //ScheduleCommand.register(this.dispatcher); // Folia - region threading -+ //ScoreboardCommand.register(this.dispatcher, context); // Folia - region threading - SeedCommand.register(this.dispatcher, selection != Commands.CommandSelection.INTEGRATED); - SetBlockCommand.register(this.dispatcher, context); - SetSpawnCommand.register(this.dispatcher); - SetWorldSpawnCommand.register(this.dispatcher); -- SpectateCommand.register(this.dispatcher); -- SpreadPlayersCommand.register(this.dispatcher); -+ //SpectateCommand.register(this.dispatcher); // Folia - region threading - TODO later -+ //SpreadPlayersCommand.register(this.dispatcher); // Folia - region threading - TODO later - StopSoundCommand.register(this.dispatcher); - SummonCommand.register(this.dispatcher, context); -- TagCommand.register(this.dispatcher); -- TeamCommand.register(this.dispatcher, context); -- TeamMsgCommand.register(this.dispatcher); -+ //TagCommand.register(this.dispatcher); // Folia - region threading - TODO later -+ //TeamCommand.register(this.dispatcher, context); // Folia - region threading - TODO later -+ //TeamMsgCommand.register(this.dispatcher); // Folia - region threading - TODO later - TeleportCommand.register(this.dispatcher); - TellRawCommand.register(this.dispatcher, context); -- TickCommand.register(this.dispatcher); -+ //TickCommand.register(this.dispatcher); // Folia - region threading - TODO later - TimeCommand.register(this.dispatcher); - TitleCommand.register(this.dispatcher, context); -- TriggerCommand.register(this.dispatcher); -+ //TriggerCommand.register(this.dispatcher); // Folia - region threading - TODO later - WeatherCommand.register(this.dispatcher); - WorldBorderCommand.register(this.dispatcher); - if (JvmProfiler.INSTANCE.isAvailable()) { -@@ -237,8 +_,8 @@ - OpCommand.register(this.dispatcher); - PardonCommand.register(this.dispatcher); - PardonIpCommand.register(this.dispatcher); -- PerfCommand.register(this.dispatcher); -- SaveAllCommand.register(this.dispatcher); -+ //PerfCommand.register(this.dispatcher); // Folia - region threading - TODO later -+ //SaveAllCommand.register(this.dispatcher); // Folia - region threading - TODO later - SaveOffCommand.register(this.dispatcher); - SaveOnCommand.register(this.dispatcher); - SetPlayerIdleTimeoutCommand.register(this.dispatcher); -@@ -480,9 +_,12 @@ - } - // Paper start - Perf: Async command map building - new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(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 bukkit, RootCommandNode rootCommandNode) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch deleted file mode 100644 index 074f14b..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java -+++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java -@@ -46,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - serverLevel.getCraftServer().getPluginManager().callEvent(event); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch deleted file mode 100644 index 4792b27..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java -+++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java -@@ -78,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack); - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(itemEntity.getDeltaMovement())); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - level.getCraftServer().getPluginManager().callEvent(event); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch deleted file mode 100644 index e326818..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch +++ /dev/null @@ -1,149 +0,0 @@ ---- a/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -89,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - serverLevel.getCraftServer().getPluginManager().callEvent(event); - } - -@@ -147,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - serverLevel.getCraftServer().getPluginManager().callEvent(event); - } - -@@ -201,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); - - org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - world.getCraftServer().getPluginManager().callEvent(event); - } - -@@ -251,7 +_,7 @@ - org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos()); - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleCopy); - org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), abstractChestedHorse.getBukkitLivingEntity()); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - world.getCraftServer().getPluginManager().callEvent(event); - } - -@@ -329,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - level.getCraftServer().getPluginManager().callEvent(event); - } - -@@ -389,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - levelAccessor.getMinecraftWorld().getCraftServer().getPluginManager().callEvent(event); - } - -@@ -425,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - serverLevel.getCraftServer().getPluginManager().callEvent(event); - } - -@@ -482,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - level.getCraftServer().getPluginManager().callEvent(event); - } - -@@ -500,7 +_,8 @@ - } - } - -- level.captureTreeGeneration = true; -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading -+ worldData.captureTreeGeneration = true; // Folia - region threading - // CraftBukkit end - if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { - this.setSuccess(false); -@@ -508,13 +_,13 @@ - level.levelEvent(1505, blockPos, 15); - } - // CraftBukkit start -- level.captureTreeGeneration = false; -- if (level.capturedBlockStates.size() > 0) { -- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; -- net.minecraft.world.level.block.SaplingBlock.treeType = null; -+ worldData.captureTreeGeneration = false; // Folia - region threading -+ if (worldData.capturedBlockStates.size() > 0) { // Folia - region threading -+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); // Folia - region threading -+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); // Folia - region threading - org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld()); -- List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); -- level.capturedBlockStates.clear(); -+ List 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); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch deleted file mode 100644 index b4c365c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -+++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -@@ -39,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack); - - org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) livingEntity.getBukkitEntity()); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - world.getCraftServer().getPluginManager().callEvent(event); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch deleted file mode 100644 index c6c876e..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java -+++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java -@@ -62,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1); - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec31.x, vec31.y, vec31.z)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - serverLevel.getCraftServer().getPluginManager().callEvent(event); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch deleted file mode 100644 index 3b5c203..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java -+++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java -@@ -32,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1); - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) direction.getStepX(), (double) direction.getStepY(), (double) direction.getStepZ())); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - serverLevel.getCraftServer().getPluginManager().callEvent(event); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch deleted file mode 100644 index d38a648..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -+++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -@@ -25,7 +_,7 @@ - org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos()); - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - serverLevel.getCraftServer().getPluginManager().callEvent(event); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch deleted file mode 100644 index 7250354..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java -+++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java -@@ -27,7 +_,7 @@ - org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockPos.getX(), blockPos.getY(), blockPos.getZ())); -- if (!DispenserBlock.eventFired) { -+ if (!DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - blockSource.level().getCraftServer().getPluginManager().callEvent(event); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/gametest/framework/GameTestHelper.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/gametest/framework/GameTestHelper.java.patch deleted file mode 100644 index bdb7f73..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/gametest/framework/GameTestHelper.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/gametest/framework/GameTestHelper.java -+++ b/net/minecraft/gametest/framework/GameTestHelper.java -@@ -306,7 +_,7 @@ - }; - Connection connection = new Connection(PacketFlow.SERVERBOUND); - new EmbeddedChannel(connection); -- this.getLevel().getServer().getPlayerList().placeNewPlayer(connection, serverPlayer, commonListenerCookie); -+ if (true) throw new UnsupportedOperationException(); // Folia - region threading - return serverPlayer; - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch deleted file mode 100644 index c98f1d5..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/gametest/framework/GameTestServer.java.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/net/minecraft/gametest/framework/GameTestServer.java -+++ b/net/minecraft/gametest/framework/GameTestServer.java -@@ -175,8 +_,12 @@ - } - - @Override -- public void tickServer(BooleanSupplier hasTimeLeft) { -- super.tickServer(hasTimeLeft); -+ // Folia start - region threading -+ public void tickServer(long startTime, long scheduledEnd, long targetBuffer, -+ io.papermc.paper.threadedregions.TickRegions.TickRegionData region) { -+ if (true) throw new UnsupportedOperationException(); -+ super.tickServer(startTime, scheduledEnd, targetBuffer, region); -+ // Folia end - region threading - ServerLevel serverLevel = this.overworld(); - if (!this.haveTestsStarted()) { - this.startTests(serverLevel); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/network/Connection.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/network/Connection.java.patch deleted file mode 100644 index 4f35b55..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/network/Connection.java.patch +++ /dev/null @@ -1,336 +0,0 @@ ---- a/net/minecraft/network/Connection.java -+++ b/net/minecraft/network/Connection.java -@@ -85,7 +_,7 @@ - private static final ProtocolInfo INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND; - private final PacketFlow receiving; - private volatile boolean sendLoginDisconnect = true; -- private final Queue pendingActions = Queues.newConcurrentLinkedQueue(); // Paper - Optimize network -+ private final Queue 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 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 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 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 queue = -+ (ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue)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 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 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. diff --git a/folia-server/minecraft-patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch deleted file mode 100644 index e051187..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/network/protocol/PacketUtils.java.patch +++ /dev/null @@ -1,37 +0,0 @@ ---- a/net/minecraft/network/protocol/PacketUtils.java -+++ b/net/minecraft/network/protocol/PacketUtils.java -@@ -20,7 +_,7 @@ - - public static void ensureRunningOnSameThread(Packet 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; - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch deleted file mode 100644 index d631317..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ /dev/null @@ -1,779 +0,0 @@ ---- a/net/minecraft/server/MinecraftServer.java -+++ b/net/minecraft/server/MinecraftServer.java -@@ -184,7 +_,7 @@ - private static final int OVERLOADED_TICKS_THRESHOLD = 20; - private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND; - private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100; -- private static final long STATUS_EXPIRE_TIME_NANOS = 5L * TimeUtil.NANOSECONDS_PER_SECOND; -+ public static final long STATUS_EXPIRE_TIME_NANOS = 5L * TimeUtil.NANOSECONDS_PER_SECOND; // Folia - region threading - public - private static final long PREPARE_LEVELS_DEFAULT_DELAY_NANOS = 10L * TimeUtil.NANOSECONDS_PER_MILLISECOND; - private static final int MAX_STATUS_PLAYER_SAMPLE = 12; - private static final int SPAWN_POSITION_SEARCH_RADIUS = 5; -@@ -222,8 +_,7 @@ - private volatile boolean running = true; - private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart - private boolean stopped; -- private int tickCount; -- private int ticksUntilAutosave = 6000; -+ // Folia - region threading - protected final Proxy proxy; - private boolean onlineMode; - private boolean preventProxyConnections; -@@ -283,7 +_,7 @@ - public org.bukkit.craftbukkit.CraftServer server; - public joptsimple.OptionSet options; - public org.bukkit.command.ConsoleCommandSender console; -- public static int currentTick; // Paper - improve tick loop -+ //public static int currentTick; // Paper - improve tick loop // Folia - region threading - public java.util.Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); - public int autosavePeriod; - // Paper - don't store the vanilla dispatcher -@@ -304,6 +_,50 @@ - private final Set 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 CompletableFuture submit(java.util.function.Supplier task) { -+ if (true) { -+ throw new UnsupportedOperationException(); -+ } -+ return super.submit(task); -+ } -+ -+ @Override -+ public CompletableFuture 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 spin(Function threadFunction) { - ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system - AtomicReference 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 players = this.playerList.getPlayers(); -+ List 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 - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/AdvancementCommands.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/AdvancementCommands.java.patch deleted file mode 100644 index 9ab05d2..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/AdvancementCommands.java.patch +++ /dev/null @@ -1,32 +0,0 @@ ---- a/net/minecraft/server/commands/AdvancementCommands.java -+++ b/net/minecraft/server/commands/AdvancementCommands.java -@@ -246,7 +_,12 @@ - int i = 0; - - for (ServerPlayer serverPlayer : targets) { -- i += action.perform(serverPlayer, advancements); -+ // Folia start - region threading -+ i += 1; -+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { -+ action.perform(player, advancements); -+ }, null, 1L); -+ // Folia end - region threading - } - - if (i == 0) { -@@ -310,9 +_,12 @@ - throw ERROR_CRITERION_NOT_FOUND.create(Advancement.name(advancement), criterionName); - } else { - for (ServerPlayer serverPlayer : targets) { -- if (action.performCriterion(serverPlayer, advancement, criterionName)) { -- i++; -- } -+ // Folia start - region threading -+ ++i; -+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { -+ action.performCriterion(player, advancement, criterionName); -+ }, null, 1L); -+ // Folia end - region threading - } - - if (i == 0) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/AttributeCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/AttributeCommand.java.patch deleted file mode 100644 index 6795ac5..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/AttributeCommand.java.patch +++ /dev/null @@ -1,188 +0,0 @@ ---- a/net/minecraft/server/commands/AttributeCommand.java -+++ b/net/minecraft/server/commands/AttributeCommand.java -@@ -266,30 +_,62 @@ - } - } - -+ // Folia start - region threading -+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) { -+ src.sendFailure((Component)ex.getRawMessage()); -+ } -+ // Folia end - region threading -+ - private static int getAttributeValue(CommandSourceStack source, Entity entity, Holder attribute, 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, 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, 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 getAttributeModifiers(Entity entity, Holder attribute) throws CommandSyntaxException { -@@ -312,11 +_,22 @@ - } - - private static int setAttributeBase(CommandSourceStack source, Entity entity, Holder 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) throws CommandSyntaxException { -@@ -338,35 +_,57 @@ - private static int addModifier( - CommandSourceStack source, Entity entity, Holder 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, 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) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ClearInventoryCommands.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ClearInventoryCommands.java.patch deleted file mode 100644 index 6612476..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ClearInventoryCommands.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/net/minecraft/server/commands/ClearInventoryCommands.java -+++ b/net/minecraft/server/commands/ClearInventoryCommands.java -@@ -65,9 +_,14 @@ - int i = 0; - - for (ServerPlayer serverPlayer : targetPlayers) { -- i += serverPlayer.getInventory().clearOrCountMatchingItems(itemPredicate, maxCount, serverPlayer.inventoryMenu.getCraftSlots()); -- serverPlayer.containerMenu.broadcastChanges(); -- serverPlayer.inventoryMenu.slotsChanged(serverPlayer.getInventory()); -+ // Folia start - region threading -+ ++i; -+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { -+ player.getInventory().clearOrCountMatchingItems(itemPredicate, maxCount, player.inventoryMenu.getCraftSlots()); -+ player.containerMenu.broadcastChanges(); -+ player.inventoryMenu.slotsChanged(player.getInventory()); -+ }, null, 1L); -+ // Folia end - region threading - } - - if (i == 0) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/DamageCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/DamageCommand.java.patch deleted file mode 100644 index 8eae6c4..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/DamageCommand.java.patch +++ /dev/null @@ -1,35 +0,0 @@ ---- a/net/minecraft/server/commands/DamageCommand.java -+++ b/net/minecraft/server/commands/DamageCommand.java -@@ -102,12 +_,29 @@ - ); - } - -+ // Folia start - region threading -+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) { -+ src.sendFailure((Component)ex.getRawMessage()); -+ } -+ // Folia end - region threading -+ - private static int damage(CommandSourceStack source, Entity target, float amount, DamageSource damageType) throws CommandSyntaxException { -- if (target.hurtServer(source.getLevel(), damageType, amount)) { -- source.sendSuccess(() -> Component.translatable("commands.damage.success", amount, target.getDisplayName()), true); -- return 1; -+ // Folia start - region threading -+ target.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> { -+ try { -+ // Folia end - region threading -+ if (nmsEntity.hurtServer(source.getLevel(), damageType, amount)) { // Folia - region threading -+ source.sendSuccess(() -> Component.translatable("commands.damage.success", amount, nmsEntity.getDisplayName()), true); // Folia - region threading -+ return; // Folia - region threading - } else { - throw ERROR_INVULNERABLE.create(); - } -+ // Folia start - region threading -+ } catch (CommandSyntaxException ex) { -+ sendMessage(source, ex); -+ } -+ }, null, 1L); -+ return 0; -+ // Folia end - region threading - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch deleted file mode 100644 index 0ad0a21..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/DefaultGameModeCommands.java.patch +++ /dev/null @@ -1,18 +0,0 @@ ---- a/net/minecraft/server/commands/DefaultGameModeCommands.java -+++ b/net/minecraft/server/commands/DefaultGameModeCommands.java -@@ -28,12 +_,14 @@ - GameType forcedGameType = server.getForcedGameType(); - if (forcedGameType != null) { - for (ServerPlayer serverPlayer : server.getPlayerList().getPlayers()) { -+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { // Folia - region threading - // Paper start - Expand PlayerGameModeChangeEvent -- org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gamemode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = player.setGameMode(gamemode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); // Folia - region threading - if (event != null && event.isCancelled()) { - commandSource.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); - } - // Paper end - Expand PlayerGameModeChangeEvent -+ }, null, 1L); // Folia - region threading - i++; - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/EffectCommands.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/EffectCommands.java.patch deleted file mode 100644 index 5297213..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/EffectCommands.java.patch +++ /dev/null @@ -1,44 +0,0 @@ ---- a/net/minecraft/server/commands/EffectCommands.java -+++ b/net/minecraft/server/commands/EffectCommands.java -@@ -180,7 +_,12 @@ - for (Entity entity : targets) { - if (entity instanceof LivingEntity) { - MobEffectInstance mobEffectInstance = new MobEffectInstance(effect, i1, amplifier, false, showParticles); -- if (((LivingEntity)entity).addEffect(mobEffectInstance, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit -+ // Folia start - region threading -+ entity.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> { -+ ((LivingEntity)nmsEntity).addEffect(mobEffectInstance, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND); -+ }, null, 1L); -+ // Folia end - region threading -+ if (true) { // CraftBukkit // Folia - region threading - i++; - } - } -@@ -210,7 +_,12 @@ - int i = 0; - - for (Entity entity : targets) { -- if (entity instanceof LivingEntity && ((LivingEntity)entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit -+ if (entity instanceof LivingEntity && true) { // CraftBukkit // Folia - region threading -+ // Folia start - region threading -+ entity.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> { -+ ((LivingEntity)nmsEntity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND); -+ }, null, 1L); -+ // Folia end - region threading - i++; - } - } -@@ -235,7 +_,12 @@ - int i = 0; - - for (Entity entity : targets) { -- if (entity instanceof LivingEntity && ((LivingEntity)entity).removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit -+ if (entity instanceof LivingEntity && true) { // CraftBukkit // Folia - region threading -+ // Folia start - region threading -+ entity.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> { -+ ((LivingEntity)nmsEntity).removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND); -+ }, null, 1L); -+ // Folia end - region threading - i++; - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/EnchantCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/EnchantCommand.java.patch deleted file mode 100644 index 449d1e4..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/EnchantCommand.java.patch +++ /dev/null @@ -1,100 +0,0 @@ ---- a/net/minecraft/server/commands/EnchantCommand.java -+++ b/net/minecraft/server/commands/EnchantCommand.java -@@ -68,51 +_,78 @@ - ); - } - -+ // Folia start - region threading -+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) { -+ src.sendFailure((Component)ex.getRawMessage()); -+ } -+ // Folia end - region threading -+ - private static int enchant(CommandSourceStack source, Collection targets, Holder 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 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, final int level, final java.util.concurrent.atomic.AtomicReference 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 - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ExperienceCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ExperienceCommand.java.patch deleted file mode 100644 index fce632c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ExperienceCommand.java.patch +++ /dev/null @@ -1,39 +0,0 @@ ---- a/net/minecraft/server/commands/ExperienceCommand.java -+++ b/net/minecraft/server/commands/ExperienceCommand.java -@@ -131,14 +_,18 @@ - } - - private static int queryExperience(CommandSourceStack source, ServerPlayer player, ExperienceCommand.Type type) { -- int i = type.query.applyAsInt(player); -- source.sendSuccess(() -> Component.translatable("commands.experience.query." + type.name, player.getDisplayName(), i), false); -- return i; -+ player.getBukkitEntity().taskScheduler.schedule((ServerPlayer serverPlayer) -> { // Folia - region threading -+ int i = type.query.applyAsInt(serverPlayer); // Folia - region threading -+ source.sendSuccess(() -> Component.translatable("commands.experience.query." + type.name, serverPlayer.getDisplayName(), i), false); // Folia - region threading -+ }, null, 1L); // Folia - region threading -+ return 0; // Folia - region threading - } - - private static int addExperience(CommandSourceStack source, Collection 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) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/FillBiomeCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/FillBiomeCommand.java.patch deleted file mode 100644 index 65c8421..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/FillBiomeCommand.java.patch +++ /dev/null @@ -1,49 +0,0 @@ ---- a/net/minecraft/server/commands/FillBiomeCommand.java -+++ b/net/minecraft/server/commands/FillBiomeCommand.java -@@ -107,6 +_,16 @@ - return fill(level, from, to, biome, biome1 -> true, message -> {}); - } - -+ // Folia start - region threading -+ private static void sendMessage(Consumer> src, Supplier> supplier) { -+ Either 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 fill( - ServerLevel level, BlockPos from, BlockPos to, Holder biome, Predicate> filter, Consumer> 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 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 - } - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch deleted file mode 100644 index 7628495..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch +++ /dev/null @@ -1,49 +0,0 @@ ---- a/net/minecraft/server/commands/FillCommand.java -+++ b/net/minecraft/server/commands/FillCommand.java -@@ -151,6 +_,12 @@ - ); - } - -+ // Folia start - region threading -+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) { -+ src.sendFailure((Component)ex.getRawMessage()); -+ } -+ // Folia end - region threading -+ - private static int fillBlocks( - CommandSourceStack source, BoundingBox area, BlockInput newBlock, FillCommand.Mode mode, @Nullable Predicate replacingPredicate - ) throws CommandSyntaxException { -@@ -161,6 +_,18 @@ - } else { - List 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 - } - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ForceLoadCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ForceLoadCommand.java.patch deleted file mode 100644 index 3add8c1..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/ForceLoadCommand.java.patch +++ /dev/null @@ -1,93 +0,0 @@ ---- a/net/minecraft/server/commands/ForceLoadCommand.java -+++ b/net/minecraft/server/commands/ForceLoadCommand.java -@@ -97,7 +_,17 @@ - ); - } - -+ // Folia start - region threading -+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) { -+ src.sendFailure((Component)ex.getRawMessage()); -+ } -+ // Folia end - region threading -+ - private static int queryForceLoad(CommandSourceStack source, ColumnPos pos) throws CommandSyntaxException { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ try { -+ // Folia end - region threading - ChunkPos chunkPos = pos.toChunkPos(); - ServerLevel level = source.getLevel(); - ResourceKey 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 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 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 - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch deleted file mode 100644 index d9b512a..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/GameModeCommand.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/net/minecraft/server/commands/GameModeCommand.java -+++ b/net/minecraft/server/commands/GameModeCommand.java -@@ -54,15 +_,18 @@ - int i = 0; - - for (ServerPlayer serverPlayer : players) { -+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer nmsEntity) -> { // Folia - region threading - // Paper start - Expand PlayerGameModeChangeEvent -- org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = nmsEntity.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); // Folia - region threading - if (event != null && !event.isCancelled()) { -- logGamemodeChange(source.getSource(), serverPlayer, gameType); -- i++; -+ logGamemodeChange(source.getSource(), nmsEntity, gameType); // Folia - region threading -+ //i++; // Folia - region threading - } else if (event != null && event.cancelMessage() != null) { - source.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); - // Paper end - Expand PlayerGameModeChangeEvent - } -+ }, null, 1L); // Folia - region threading -+ ++i; // Folia - region threading - } - - return i; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/GiveCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/GiveCommand.java.patch deleted file mode 100644 index 6765546..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/GiveCommand.java.patch +++ /dev/null @@ -1,47 +0,0 @@ ---- a/net/minecraft/server/commands/GiveCommand.java -+++ b/net/minecraft/server/commands/GiveCommand.java -@@ -65,32 +_,34 @@ - int min = Math.min(maxStackSize, i1); - i1 -= min; - ItemStack itemStack1 = item.createItemStack(min, false); -- boolean flag = serverPlayer.getInventory().add(itemStack1); -+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer nmsEntity) -> { // Folia - region threading -+ boolean flag = nmsEntity.getInventory().add(itemStack1); // Folia - region threading - if (flag && itemStack1.isEmpty()) { -- ItemEntity itemEntity = serverPlayer.drop(itemStack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event -+ ItemEntity itemEntity = nmsEntity.drop(itemStack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event // Folia - region threading - if (itemEntity != null) { - itemEntity.makeFakeItem(); - } - -- serverPlayer.level() -+ nmsEntity.level() // Folia - region threading - .playSound( - null, -- serverPlayer.getX(), -- serverPlayer.getY(), -- serverPlayer.getZ(), -+ nmsEntity.getX(), // Folia - region threading -+ nmsEntity.getY(), // Folia - region threading -+ nmsEntity.getZ(), // Folia - region threading - SoundEvents.ITEM_PICKUP, - SoundSource.PLAYERS, - 0.2F, -- ((serverPlayer.getRandom().nextFloat() - serverPlayer.getRandom().nextFloat()) * 0.7F + 1.0F) * 2.0F -+ ((nmsEntity.getRandom().nextFloat() - nmsEntity.getRandom().nextFloat()) * 0.7F + 1.0F) * 2.0F // Folia - region threading - ); -- serverPlayer.containerMenu.broadcastChanges(); -+ nmsEntity.containerMenu.broadcastChanges(); // Folia - region threading - } else { -- ItemEntity itemEntity = serverPlayer.drop(itemStack1, false); -+ ItemEntity itemEntity = nmsEntity.drop(itemStack1, false); // Folia - region threading - if (itemEntity != null) { - itemEntity.setNoPickUpDelay(); -- itemEntity.setTarget(serverPlayer.getUUID()); -+ itemEntity.setTarget(nmsEntity.getUUID()); // Folia - region threading - } - } -+ }, null, 1L); // Folia - region threading - } - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/KillCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/KillCommand.java.patch deleted file mode 100644 index 37de3a2..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/KillCommand.java.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- a/net/minecraft/server/commands/KillCommand.java -+++ b/net/minecraft/server/commands/KillCommand.java -@@ -24,7 +_,9 @@ - - private static int kill(CommandSourceStack source, Collection 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) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch deleted file mode 100644 index 5157b63..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/PlaceCommand.java.patch +++ /dev/null @@ -1,134 +0,0 @@ ---- a/net/minecraft/server/commands/PlaceCommand.java -+++ b/net/minecraft/server/commands/PlaceCommand.java -@@ -233,36 +_,79 @@ - ); - } - -+ // Folia start - region threading -+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) { -+ src.sendFailure((Component)ex.getRawMessage()); -+ } -+ // Folia end - region threading -+ - public static int placeFeature(CommandSourceStack source, Holder.Reference> 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 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, 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 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 { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/RecipeCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/RecipeCommand.java.patch deleted file mode 100644 index 5d2f509..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/RecipeCommand.java.patch +++ /dev/null @@ -1,30 +0,0 @@ ---- a/net/minecraft/server/commands/RecipeCommand.java -+++ b/net/minecraft/server/commands/RecipeCommand.java -@@ -81,7 +_,12 @@ - int i = 0; - - for (ServerPlayer serverPlayer : targets) { -- i += serverPlayer.awardRecipes(recipes); -+ // Folia start - region threading -+ ++i; -+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { -+ player.awardRecipes(recipes); -+ }, null, 1L); -+ // Folia end - region threading - } - - if (i == 0) { -@@ -103,7 +_,12 @@ - int i = 0; - - for (ServerPlayer serverPlayer : targets) { -- i += serverPlayer.resetRecipes(recipes); -+ // Folia start - region threading -+ ++i; -+ serverPlayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { -+ player.resetRecipes(recipes); -+ }, null, 1L); -+ // Folia end - region threading - } - - if (i == 0) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch deleted file mode 100644 index 3169fa2..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch +++ /dev/null @@ -1,42 +0,0 @@ ---- a/net/minecraft/server/commands/SetBlockCommand.java -+++ b/net/minecraft/server/commands/SetBlockCommand.java -@@ -80,10 +_,21 @@ - ); - } - -+ // Folia start - region threading -+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) { -+ src.sendFailure((Component)ex.getRawMessage()); -+ } -+ // Folia end - region threading -+ - private static int setBlock( - CommandSourceStack source, BlockPos pos, BlockInput state, SetBlockCommand.Mode mode, @Nullable Predicate 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 { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch deleted file mode 100644 index d69276a..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SetSpawnCommand.java.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/net/minecraft/server/commands/SetSpawnCommand.java -+++ b/net/minecraft/server/commands/SetSpawnCommand.java -@@ -69,7 +_,11 @@ - final Collection 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 diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SummonCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SummonCommand.java.patch deleted file mode 100644 index cfe4a7c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/SummonCommand.java.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- a/net/minecraft/server/commands/SummonCommand.java -+++ b/net/minecraft/server/commands/SummonCommand.java -@@ -88,12 +_,18 @@ - if (entity == null) { - throw ERROR_FAILED.create(); - } else { -- if (randomizeProperties && entity instanceof Mob) { -- ((Mob)entity) -- .finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.COMMAND, null); -- } -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( -+ level, entity.chunkPosition().x, entity.chunkPosition().z, () -> { -+ if (randomizeProperties && entity instanceof Mob) { -+ ((Mob)entity) -+ .finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.COMMAND, null); -+ } -+ level.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND); -+ }); -+ // Folia end - region threading - -- if (!level.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND)) { // CraftBukkit - pass a spawn reason of "COMMAND" -+ if (false) { // CraftBukkit - pass a spawn reason of "COMMAND" // Folia - region threading - throw ERROR_DUPLICATE_UUID.create(); - } else { - return entity; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch deleted file mode 100644 index d3f4350..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch +++ /dev/null @@ -1,47 +0,0 @@ ---- a/net/minecraft/server/commands/TeleportCommand.java -+++ b/net/minecraft/server/commands/TeleportCommand.java -@@ -154,18 +_,7 @@ - - private static int teleportToEntity(CommandSourceStack source, Collection 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) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/TimeCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/TimeCommand.java.patch deleted file mode 100644 index 9be48a8..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/TimeCommand.java.patch +++ /dev/null @@ -1,33 +0,0 @@ ---- a/net/minecraft/server/commands/TimeCommand.java -+++ b/net/minecraft/server/commands/TimeCommand.java -@@ -56,6 +_,7 @@ - } - - public static int setTime(CommandSourceStack source, int time) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading - for (ServerLevel serverLevel : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) { // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change - // serverLevel.setDayTime(time); - // CraftBukkit start -@@ -69,10 +_,12 @@ - - source.getServer().forceTimeSynchronization(); - source.sendSuccess(() -> Component.translatable("commands.time.set", time), true); -- return getDayTime(source.getLevel()); -+ }); // Folia - region threading -+ return 0; // Folia - region threading - } - - public static int addTime(CommandSourceStack source, int amount) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading - for (ServerLevel serverLevel : io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels() : java.util.List.of(source.getLevel())) { // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change - // CraftBukkit start - org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(serverLevel.getWorld(), org.bukkit.event.world.TimeSkipEvent.SkipReason.COMMAND, amount); -@@ -86,6 +_,7 @@ - source.getServer().forceTimeSynchronization(); - int dayTime = getDayTime(source.getLevel()); - source.sendSuccess(() -> Component.translatable("commands.time.set", dayTime), true); -- return dayTime; -+ }); // Folia - region threading -+ return 0; // Folia - region threading - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch deleted file mode 100644 index 62942b7..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/WeatherCommand.java.patch +++ /dev/null @@ -1,29 +0,0 @@ ---- a/net/minecraft/server/commands/WeatherCommand.java -+++ b/net/minecraft/server/commands/WeatherCommand.java -@@ -48,20 +_,26 @@ - } - - private static int setClear(CommandSourceStack source, int time) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading - source.getLevel().setWeatherParameters(getDuration(source, time, ServerLevel.RAIN_DELAY), 0, false, false); // CraftBukkit - SPIGOT-7680: per-world - source.sendSuccess(() -> Component.translatable("commands.weather.set.clear"), true); -+ }); // Folia - region threading - return time; - } - - private static int setRain(CommandSourceStack source, int time) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading - source.getLevel().setWeatherParameters(0, getDuration(source, time, ServerLevel.RAIN_DURATION), true, false); // CraftBukkit - SPIGOT-7680: per-world - source.sendSuccess(() -> Component.translatable("commands.weather.set.rain"), true); -+ }); // Folia - region threading - return time; - } - - private static int setThunder(CommandSourceStack source, int time) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading - source.getLevel().setWeatherParameters(0, getDuration(source, time, ServerLevel.THUNDER_DURATION), true, true); // CraftBukkit - SPIGOT-7680: per-world - source.sendSuccess(() -> Component.translatable("commands.weather.set.thunder"), true); -+ }); // Folia - region threading - return time; - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch deleted file mode 100644 index 47f4079..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/commands/WorldBorderCommand.java.patch +++ /dev/null @@ -1,169 +0,0 @@ ---- a/net/minecraft/server/commands/WorldBorderCommand.java -+++ b/net/minecraft/server/commands/WorldBorderCommand.java -@@ -134,18 +_,39 @@ - ); - } - -+ // Folia start - region threading -+ private static void sendMessage(CommandSourceStack src, CommandSyntaxException ex) { -+ src.sendFailure((Component)ex.getRawMessage()); -+ } -+ // Folia end - region threading -+ - private static int setDamageBuffer(CommandSourceStack source, float distance) throws CommandSyntaxException { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ try { -+ // Folia end - region threading - WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit - if (worldBorder.getDamageSafeZone() == distance) { - throw ERROR_SAME_DAMAGE_BUFFER.create(); - } else { - worldBorder.setDamageSafeZone(distance); - source.sendSuccess(() -> Component.translatable("commands.worldborder.damage.buffer.success", String.format(Locale.ROOT, "%.2f", distance)), true); -- return (int)distance; -+ return; // Folia - region threading - } -+ // Folia start - region threading -+ } catch (CommandSyntaxException ex) { -+ sendMessage(source, ex); -+ } -+ }); -+ return 1; -+ // Folia end - region threading - } - - private static int setDamageAmount(CommandSourceStack source, float damagePerBlock) throws CommandSyntaxException { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ try { -+ // Folia end - region threading - WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit - if (worldBorder.getDamagePerBlock() == damagePerBlock) { - throw ERROR_SAME_DAMAGE_AMOUNT.create(); -@@ -154,39 +_,79 @@ - source.sendSuccess( - () -> Component.translatable("commands.worldborder.damage.amount.success", String.format(Locale.ROOT, "%.2f", damagePerBlock)), true - ); -- return (int)damagePerBlock; -+ return; // Folia - region threading - } -+ // Folia start - region threading -+ } catch (CommandSyntaxException ex) { -+ sendMessage(source, ex); -+ } -+ }); -+ return 1; -+ // Folia end - region threading - } - - private static int setWarningTime(CommandSourceStack source, int time) throws CommandSyntaxException { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ try { -+ // Folia end - region threading - WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit - if (worldBorder.getWarningTime() == time) { - throw ERROR_SAME_WARNING_TIME.create(); - } else { - worldBorder.setWarningTime(time); - source.sendSuccess(() -> Component.translatable("commands.worldborder.warning.time.success", time), true); -- return time; -+ return; // Folia - region threading - } -+ // Folia start - region threading -+ } catch (CommandSyntaxException ex) { -+ sendMessage(source, ex); -+ } -+ }); -+ return 1; -+ // Folia end - region threading - } - - private static int setWarningDistance(CommandSourceStack source, int distance) throws CommandSyntaxException { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ try { -+ // Folia end - region threading - WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit - if (worldBorder.getWarningBlocks() == distance) { - throw ERROR_SAME_WARNING_DISTANCE.create(); - } else { - worldBorder.setWarningBlocks(distance); - source.sendSuccess(() -> Component.translatable("commands.worldborder.warning.distance.success", distance), true); -- return distance; -+ return; // Folia - region threading - } -+ // Folia start - region threading -+ } catch (CommandSyntaxException ex) { -+ sendMessage(source, ex); -+ } -+ }); -+ return 1; -+ // Folia end - region threading - } - - private static int getSize(CommandSourceStack source) { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ // Folia end - region threading - double size = source.getLevel().getWorldBorder().getSize(); // CraftBukkit - source.sendSuccess(() -> Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", size)), false); -- return Mth.floor(size + 0.5); -+ return; // Folia - region threading -+ // Folia start - region threading -+ }); -+ return 1; -+ // Folia end - region threading - } - - private static int setCenter(CommandSourceStack source, Vec2 pos) throws CommandSyntaxException { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ try { -+ // Folia end - region threading - WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit - if (worldBorder.getCenterX() == pos.x && worldBorder.getCenterZ() == pos.y) { - throw ERROR_SAME_CENTER.create(); -@@ -198,13 +_,24 @@ - ), - true - ); -- return 0; -+ return; // Folia - region threading - } else { - throw ERROR_TOO_FAR_OUT.create(); - } -+ // Folia start - region threading -+ } catch (CommandSyntaxException ex) { -+ sendMessage(source, ex); -+ } -+ }); -+ return 1; -+ // Folia end - region threading - } - - private static int setSize(CommandSourceStack source, double newSize, long time) throws CommandSyntaxException { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ try { -+ // Folia end - region threading - WorldBorder worldBorder = source.getLevel().getWorldBorder(); // CraftBukkit - double size = worldBorder.getSize(); - if (size == newSize) { -@@ -234,7 +_,14 @@ - source.sendSuccess(() -> Component.translatable("commands.worldborder.set.immediate", String.format(Locale.ROOT, "%.1f", newSize)), true); - } - -- return (int)(newSize - size); -+ return; // Folia - region threading - } -+ // Folia start - region threading -+ } catch (CommandSyntaxException ex) { -+ sendMessage(source, ex); -+ } -+ }); -+ return 1; -+ // Folia end - region threading - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch deleted file mode 100644 index ca927db..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ /dev/null @@ -1,39 +0,0 @@ ---- a/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/net/minecraft/server/dedicated/DedicatedServer.java -@@ -425,7 +_,7 @@ - @Override - public void tickConnection() { - super.tickConnection(); -- this.handleConsoleInputs(); -+ // Folia - region threading - } - - @Override -@@ -732,7 +_,8 @@ - - public String runCommand(RconConsoleSource rconConsoleSource, String s) { - rconConsoleSource.prepareForCommand(); -- this.executeBlocking(() -> { -+ final java.util.concurrent.atomic.AtomicReference 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 - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch deleted file mode 100644 index 91ab3fb..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ /dev/null @@ -1,227 +0,0 @@ ---- a/net/minecraft/server/level/ChunkMap.java -+++ b/net/minecraft/server/level/ChunkMap.java -@@ -128,8 +_,8 @@ - public final ChunkMap.DistanceManager distanceManager; - public final AtomicInteger tickingGenerated = new AtomicInteger(); // Paper - public - private final String storageName; -- private final PlayerMap playerMap = new PlayerMap(); -- public final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); -+ //private final PlayerMap playerMap = new PlayerMap(); // Folia - region threading -+ //public final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); // Folia - region threading - private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); - // Paper - rewrite chunk system - public int serverViewDistance; -@@ -797,12 +_,12 @@ - - void updatePlayerStatus(ServerPlayer player, boolean track) { - boolean flag = this.skipPlayer(player); -- boolean flag1 = this.playerMap.ignoredOrUnknown(player); -+ //boolean flag1 = this.playerMap.ignoredOrUnknown(player); // Folia - region threading - if (track) { -- this.playerMap.addPlayer(player, flag); -+ //this.playerMap.addPlayer(player, flag); // Folia - region threading - this.updatePlayerPos(player); - if (!flag) { -- this.distanceManager.addPlayer(SectionPos.of(player), player); -+ //this.distanceManager.addPlayer(SectionPos.of(player), player); // Folia - region threading - ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$addPlayer(player, SectionPos.of(player)); // Paper - chunk tick iteration optimisation - } - -@@ -810,9 +_,9 @@ - ca.spottedleaf.moonrise.common.PlatformHooks.get().addPlayerToDistanceMaps(this.level, player); // Paper - rewrite chunk system - } else { - SectionPos lastSectionPos = player.getLastSectionPos(); -- this.playerMap.removePlayer(player); -- if (!flag1) { -- this.distanceManager.removePlayer(lastSectionPos, player); -+ //this.playerMap.removePlayer(player); // Folia - region threading -+ if (true) { // Folia - region threading -+ //this.distanceManager.removePlayer(lastSectionPos, player); // Folia - region threading - ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$removePlayer(player, SectionPos.of(player)); // Paper - chunk tick iteration optimisation - } - -@@ -830,27 +_,13 @@ - - SectionPos lastSectionPos = player.getLastSectionPos(); - SectionPos sectionPos = SectionPos.of(player); -- boolean flag = this.playerMap.ignored(player); -+ //boolean flag = this.playerMap.ignored(player); // Folia - region threading - boolean flag1 = this.skipPlayer(player); -- boolean flag2 = lastSectionPos.asLong() != sectionPos.asLong(); -- if (flag2 || flag != flag1) { -+ //boolean flag2 = lastSectionPos.asLong() != sectionPos.asLong(); // Folia - region threading -+ if (true) { // Folia - region threading - this.updatePlayerPos(player); -- ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$updatePlayer(player, lastSectionPos, sectionPos, flag, flag1); // Paper - chunk tick iteration optimisation -- if (!flag) { -- this.distanceManager.removePlayer(lastSectionPos, player); -- } -- -- if (!flag1) { -- this.distanceManager.addPlayer(sectionPos, player); -- } -- -- if (!flag && flag1) { -- this.playerMap.ignorePlayer(player); -- } -- -- if (flag && !flag1) { -- this.playerMap.unIgnorePlayer(player); -- } -+ ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager)this.distanceManager).moonrise$updatePlayer(player, lastSectionPos, sectionPos, false, flag1); // Paper - chunk tick iteration optimisation // Folia - region threading -+ // Folia - region threading - - // Paper - rewrite chunk system - } -@@ -880,9 +_,9 @@ - public void addEntity(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot - // Paper start - ignore and warn about illegal addEntity calls instead of crashing server -- if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { -+ if (!entity.valid || entity.level() != this.level || entity.moonrise$getTrackedEntity() != null) { // Folia - region threading - LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() -- + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); -+ + ": " + entity + (entity.moonrise$getTrackedEntity() != null ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); // Folia - region threading - return; - } - // Paper end - ignore and warn about illegal addEntity calls instead of crashing server -@@ -893,22 +_,28 @@ - i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot - if (i != 0) { - int updateInterval = type.updateInterval(); -- if (this.entityMap.containsKey(entity.getId())) { -+ if (entity.moonrise$getTrackedEntity() != null) { // Folia - region threading - throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Entity is already tracked!")); - } else { - ChunkMap.TrackedEntity trackedEntity = new ChunkMap.TrackedEntity(entity, i, updateInterval, type.trackDeltas()); -- this.entityMap.put(entity.getId(), trackedEntity); -+ //this.entityMap.put(entity.getId(), trackedEntity); // Folia - region threading - // Paper start - optimise entity tracker - if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$getTrackedEntity() != null) { - throw new IllegalStateException("Entity is already tracked"); - } - ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(trackedEntity); - // Paper end - optimise entity tracker -- trackedEntity.updatePlayers(this.level.players()); -+ trackedEntity.updatePlayers(this.level.getLocalPlayers()); // Folia - region threading - if (entity instanceof ServerPlayer serverPlayer) { - this.updatePlayerStatus(serverPlayer, true); - -- for (ChunkMap.TrackedEntity trackedEntity1 : this.entityMap.values()) { -+ // Folia start - region threading -+ for (Entity possible : this.level.getCurrentWorldData().trackerEntities) { -+ ChunkMap.TrackedEntity trackedEntity1 = possible.moonrise$getTrackedEntity(); -+ if (trackedEntity1 == null) { -+ continue; -+ } -+ // Folia end - region threading - if (trackedEntity1.entity != serverPlayer) { - trackedEntity1.updatePlayer(serverPlayer); - } -@@ -924,12 +_,19 @@ - if (entity instanceof ServerPlayer serverPlayer) { - this.updatePlayerStatus(serverPlayer, false); - -- for (ChunkMap.TrackedEntity trackedEntity : this.entityMap.values()) { -+ // Folia start - region threading -+ for (Entity possible : this.level.getCurrentWorldData().getLocalEntities()) { -+ ChunkMap.TrackedEntity trackedEntity = possible.moonrise$getTrackedEntity(); -+ if (trackedEntity == null) { -+ continue; -+ } -+ // Folia end - region threading - trackedEntity.removePlayer(serverPlayer); - } -+ // Folia end - region threading - } - -- ChunkMap.TrackedEntity trackedEntity1 = this.entityMap.remove(entity.getId()); -+ ChunkMap.TrackedEntity trackedEntity1 = entity.moonrise$getTrackedEntity(); // Folia - region threading - if (trackedEntity1 != null) { - trackedEntity1.broadcastRemoved(); - } -@@ -938,9 +_,11 @@ - - // Paper start - optimise entity tracker - private void newTrackerTick() { -+ final io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.level.getCurrentWorldData(); // Folia - region threading - final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup)((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();; -+ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = this.level.moonrise$getNearbyPlayers(); // Folia - region threading - -- final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = entityLookup.trackerEntities; -+ final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = worldData.trackerEntities; // Folia - region threading - final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); - for (int i = 0, len = trackerEntities.size(); i < len; ++i) { - final Entity entity = trackerEntitiesRaw[i]; -@@ -948,7 +_,7 @@ - if (tracker == null) { - continue; - } -- ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkData().nearbyPlayers); -+ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); // Folia - region threading - if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity)tracker).moonrise$hasPlayers() - || ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) { - tracker.serverEntity.sendChanges(); -@@ -966,44 +_,18 @@ - // Paper end - optimise entity tracker - // Paper - rewrite chunk system - -- List list = Lists.newArrayList(); -- List 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 diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/DistanceManager.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/DistanceManager.java.patch deleted file mode 100644 index 5be09f6..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/DistanceManager.java.patch +++ /dev/null @@ -1,53 +0,0 @@ ---- a/net/minecraft/server/level/DistanceManager.java -+++ b/net/minecraft/server/level/DistanceManager.java -@@ -57,16 +_,16 @@ - } - // Paper end - rewrite chunk system - // Paper start - chunk tick iteration optimisation -- private final ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap 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() { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch deleted file mode 100644 index 07a9bd4..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch +++ /dev/null @@ -1,276 +0,0 @@ ---- a/net/minecraft/server/level/ServerChunkCache.java -+++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -61,18 +_,14 @@ - public final ServerChunkCache.MainThreadExecutor mainThreadProcessor; - public final ChunkMap chunkMap; - private final DimensionDataStorage dataStorage; -- private long lastInhabitedUpdate; -+ //private long lastInhabitedUpdate; // Folia - region threading - public boolean spawnEnemies = true; - public boolean spawnFriendlies = true; - private static final int CACHE_SIZE = 4; - private final long[] lastChunkPos = new long[4]; - private final ChunkStatus[] lastChunkStatus = new ChunkStatus[4]; - private final ChunkAccess[] lastChunk = new ChunkAccess[4]; -- private final List tickingChunks = new ArrayList<>(); -- private final Set chunkHoldersToBroadcast = new ReferenceOpenHashSet<>(); -- @Nullable -- @VisibleForDebug -- private NaturalSpawner.SpawnState lastSpawnState; -+ // Folia - moved to regionised world data - // Paper start - private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable 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 completable = new CompletableFuture<>(); - chunkTaskScheduler.scheduleChunkLoad( -@@ -154,9 +_,7 @@ - // Paper start - chunk tick iteration optimisations - private final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom shuffleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(0L); - private boolean isChunkNearPlayer(final ChunkMap chunkMap, final ChunkPos chunkPos, final LevelChunk levelChunk) { -- final ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData chunkData = ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)levelChunk).moonrise$getChunkAndHolder().holder()) -- .moonrise$getRealChunkHolder().holderData; -- final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk nearbyPlayers = chunkData.nearbyPlayers; -+ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk nearbyPlayers = this.level.moonrise$getNearbyPlayers().getChunk(chunkPos); // Folia - region threading - if (nearbyPlayers == null) { - return false; - } -@@ -355,6 +_,7 @@ - } - - public CompletableFuture> getChunkFuture(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) { -+ if (true) throw new UnsupportedOperationException(); // Folia - region threading - boolean flag = Thread.currentThread() == this.mainThread; - CompletableFuture> 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 list = this.tickingChunks; -+ List 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 output) { - // Paper start - chunk tick iteration optimisation - final ca.spottedleaf.moonrise.common.list.ReferenceList 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 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 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 void addRegionTicket(TicketType 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 CompletableFuture submit(Supplier task) { -+ if (true) { -+ throw new UnsupportedOperationException(); -+ } -+ return super.submit(task); -+ } -+ -+ @Override -+ public CompletableFuture 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 - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerEntityGetter.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerEntityGetter.java.patch deleted file mode 100644 index d375195..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerEntityGetter.java.patch +++ /dev/null @@ -1,32 +0,0 @@ ---- a/net/minecraft/server/level/ServerEntityGetter.java -+++ b/net/minecraft/server/level/ServerEntityGetter.java -@@ -14,17 +_,17 @@ - - @Nullable - default Player getNearestPlayer(TargetingConditions targetingConditions, LivingEntity source) { -- return this.getNearestEntity(this.players(), targetingConditions, source, source.getX(), source.getY(), source.getZ()); -+ return this.getNearestEntity(this.getLocalPlayers(), targetingConditions, source, source.getX(), source.getY(), source.getZ()); // Folia - region threading - } - - @Nullable - default Player getNearestPlayer(TargetingConditions targetingConditions, LivingEntity source, double x, double y, double z) { -- return this.getNearestEntity(this.players(), targetingConditions, source, x, y, z); -+ return this.getNearestEntity(this.getLocalPlayers(), targetingConditions, source, x, y, z); // Folia - region threading - } - - @Nullable - default Player getNearestPlayer(TargetingConditions targetingConditions, double x, double y, double z) { -- return this.getNearestEntity(this.players(), targetingConditions, null, x, y, z); -+ return this.getNearestEntity(this.getLocalPlayers(), targetingConditions, null, x, y, z); // Folia - region threading - } - - @Nullable -@@ -57,7 +_,7 @@ - default List getNearbyPlayers(TargetingConditions targetingConditions, LivingEntity source, AABB area) { - List 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); - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch deleted file mode 100644 index 84004b2..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch +++ /dev/null @@ -1,1133 +0,0 @@ ---- a/net/minecraft/server/level/ServerLevel.java -+++ b/net/minecraft/server/level/ServerLevel.java -@@ -179,42 +_,40 @@ - private static final Logger LOGGER = LogUtils.getLogger(); - private static final int EMPTY_TIME_NO_TICK = 300; - private static final int MAX_SCHEDULED_TICKS_PER_TICK = 65536; -- final List players = Lists.newArrayList(); -+ final List players = new java.util.concurrent.CopyOnWriteArrayList<>(); // Folia - region threading - public final ServerChunkCache chunkSource; - private final MinecraftServer server; - public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type - private int lastSpawnChunkRadius; -- final EntityTickList entityTickList = new EntityTickList(); -+ //final EntityTickList entityTickList = new EntityTickList(); // Folia - region threading - // Paper - rewrite chunk system - private final GameEventDispatcher gameEventDispatcher; - public boolean noSave; - private final SleepStatus sleepStatus; - private int emptyTime; - private final PortalForcer portalForcer; -- private final LevelTicks blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); -- private final LevelTicks fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); -- private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); -- final Set navigatingMobs = new ObjectOpenHashSet<>(); -+ //private final LevelTicks blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); // Folia - region threading -+ //private final LevelTicks fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); // Folia - region threading -+ //private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); // Folia - region threading -+ //final Set navigatingMobs = new ObjectOpenHashSet<>(); // Folia - region threading - volatile boolean isUpdatingNavigations; - protected final Raids raids; -- private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); -- private final List blockEventsToReschedule = new ArrayList<>(64); -- private boolean handlingTick; -+ //private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); // Folia - region threading -+ //private final List blockEventsToReschedule = new ArrayList<>(64); // Folia - region threading -+ //private boolean handlingTick; // Folia - region threading - private final List customSpawners; - @Nullable - private EndDragonFight dragonFight; -- final Int2ObjectMap dragonParts = new Int2ObjectOpenHashMap<>(); -+ final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable dragonParts = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); // Folia - region threading - private final StructureManager structureManager; - private final StructureCheck structureCheck; -- private final boolean tickTime; -+ public final boolean tickTime; // Folia - region threading - private final RandomSequences randomSequences; - - // CraftBukkit start - public final LevelStorageSource.LevelStorageAccess levelStorageAccess; - public final UUID uuid; -- public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent -- public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent -- private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) -+ // Folia - region threading - move to regionised world data - - public LevelChunk getChunkIfLoaded(int x, int z) { - return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -242,6 +_,13 @@ - int minChunkZ = minBlockZ >> 4; - int maxChunkZ = maxBlockZ >> 4; - -+ // Folia start - region threading -+ // don't let players move into regions not owned -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, minChunkX, minChunkZ, maxChunkX, maxChunkZ)) { -+ return false; -+ } -+ // Folia end - region threading -+ - ServerChunkCache chunkProvider = this.getChunkSource(); - - for (int cx = minChunkX; cx <= maxChunkX; ++cx) { -@@ -297,11 +_,7 @@ - private final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler; - private long lastMidTickFailure; - private long tickedBlocksOrFluids; -- private final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = new ca.spottedleaf.moonrise.common.misc.NearbyPlayers((ServerLevel)(Object)this); -- private static final ServerChunkCache.ChunkAndHolder[] EMPTY_CHUNK_AND_HOLDERS = new ServerChunkCache.ChunkAndHolder[0]; -- private final ca.spottedleaf.moonrise.common.list.ReferenceList loadedChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); -- private final ca.spottedleaf.moonrise.common.list.ReferenceList tickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); -- private final ca.spottedleaf.moonrise.common.list.ReferenceList entityTickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); -+ // Folia - region threading - move to regionized data - - @Override - public final LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) { -@@ -359,7 +_,7 @@ - - @Override - public final int moonrise$getRegionChunkShift() { -- return io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift(); -+ return this.regioniser.sectionChunkShift; // Folia - region threading - } - - @Override -@@ -460,22 +_,22 @@ - - @Override - public final ca.spottedleaf.moonrise.common.misc.NearbyPlayers moonrise$getNearbyPlayers() { -- return this.nearbyPlayers; -+ return this.getCurrentWorldData().getNearbyPlayers(); // Folia - region threading - } - - @Override - public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getLoadedChunks() { -- return this.loadedChunks; -+ return this.getCurrentWorldData().getChunks(); // Folia - region threading - } - - @Override - public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getTickingChunks() { -- return this.tickingChunks; -+ return this.getCurrentWorldData().getTickingChunks(); // Folia - region threading - } - - @Override - public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getEntityTickingChunks() { -- return this.entityTickingChunks; -+ return this.getCurrentWorldData().getEntityTickingChunks(); // Folia - region threading - } - - @Override -@@ -495,80 +_,85 @@ - // Paper end - rewrite chunk system - // Paper start - chunk tick iteration - private static final ServerChunkCache.ChunkAndHolder[] EMPTY_PLAYER_CHUNK_HOLDERS = new ServerChunkCache.ChunkAndHolder[0]; -- private final ca.spottedleaf.moonrise.common.list.ReferenceList playerTickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_PLAYER_CHUNK_HOLDERS); -- private final it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap playerTickingRequests = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); -+ // Folia - region threading - - @Override - public final ca.spottedleaf.moonrise.common.list.ReferenceList moonrise$getPlayerTickingChunks() { -- return this.playerTickingChunks; -+ throw new UnsupportedOperationException(); // Folia - region threading - } - - @Override - public final void moonrise$markChunkForPlayerTicking(final LevelChunk chunk) { -- final ChunkPos pos = chunk.getPos(); -- if (!this.playerTickingRequests.containsKey(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos))) { -- return; -- } -- -- this.playerTickingChunks.add(((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()); -+ // Folia - region threading - } - - @Override - public final void moonrise$removeChunkForPlayerTicking(final LevelChunk chunk) { -- this.playerTickingChunks.remove(((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()); -+ // Folia - region threading - } - - @Override - public final void moonrise$addPlayerTickingRequest(final int chunkX, final int chunkZ) { -- ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot add ticking request async"); -- -- final long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ); -- -- if (this.playerTickingRequests.addTo(chunkKey, 1) != 0) { -- // already added -- return; -- } -- -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler() -- .chunkHolderManager.getChunkHolder(chunkKey); -- -- if (chunkHolder == null || !chunkHolder.isTickingReady()) { -- return; -- } -- -- this.playerTickingChunks.add( -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder() -- ); -+ // Folia - region threading - } - - @Override - public final void moonrise$removePlayerTickingRequest(final int chunkX, final int chunkZ) { -- ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot remove ticking request async"); -- -- final long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ); -- final int val = this.playerTickingRequests.addTo(chunkKey, -1); -- -- if (val <= 0) { -- throw new IllegalStateException("Negative counter"); -- } -- -- if (val != 1) { -- // still has at least one request -- return; -- } -- -- final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler() -- .chunkHolderManager.getChunkHolder(chunkKey); -- -- if (chunkHolder == null || !chunkHolder.isTickingReady()) { -- return; -- } -- -- this.playerTickingChunks.remove( -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder() -- ); -+ // Folia - region threading - } - // Paper end - chunk tick iteration -+ // Folia start - region threading -+ public final io.papermc.paper.threadedregions.TickRegions tickRegions = new io.papermc.paper.threadedregions.TickRegions(); -+ public final io.papermc.paper.threadedregions.ThreadedRegionizer regioniser; -+ { -+ this.regioniser = new io.papermc.paper.threadedregions.ThreadedRegionizer<>( -+ (int)Math.max(1L, (8L * 16L * 16L) / (1L << (2 * (io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift())))), -+ (1.0 / 6.0), -+ Math.max(1, 8 / (1 << io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift())), -+ 1, -+ io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift(), -+ this, -+ this.tickRegions -+ ); -+ } -+ public final io.papermc.paper.threadedregions.RegionizedTaskQueue.WorldRegionTaskData taskQueueRegionData = new io.papermc.paper.threadedregions.RegionizedTaskQueue.WorldRegionTaskData(this); -+ public static final int WORLD_INIT_NOT_CHECKED = 0; -+ public static final int WORLD_INIT_CHECKING = 1; -+ public static final int WORLD_INIT_CHECKED = 2; -+ public final java.util.concurrent.atomic.AtomicInteger checkInitialised = new java.util.concurrent.atomic.AtomicInteger(WORLD_INIT_NOT_CHECKED); -+ public ChunkPos randomSpawnSelection; -+ -+ public static final record PendingTeleport(Entity.EntityTreeNode rootVehicle, Vec3 to) {} -+ private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet pendingTeleports = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); -+ -+ public void pushPendingTeleport(final PendingTeleport teleport) { -+ synchronized (this.pendingTeleports) { -+ this.pendingTeleports.add(teleport); -+ } -+ } -+ -+ public boolean removePendingTeleport(final PendingTeleport teleport) { -+ synchronized (this.pendingTeleports) { -+ return this.pendingTeleports.remove(teleport); -+ } -+ } -+ -+ public List removeAllRegionTeleports() { -+ final List ret = new ArrayList<>(); -+ -+ synchronized (this.pendingTeleports) { -+ for (final java.util.Iterator iterator = this.pendingTeleports.iterator(); iterator.hasNext(); ) { -+ final PendingTeleport pendingTeleport = iterator.next(); -+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, pendingTeleport.to())) { -+ ret.add(pendingTeleport); -+ iterator.remove(); -+ } -+ } -+ } -+ -+ return ret; -+ } -+ // Folia end - region threading - - public ServerLevel( - MinecraftServer server, -@@ -633,7 +_,7 @@ - ); - this.chunkSource.getGeneratorState().ensureStructuresGenerated(); - this.portalForcer = new PortalForcer(this); -- this.updateSkyBrightness(); -+ //this.updateSkyBrightness(); // Folia - region threading - delay until first tick - this.prepareWeather(); - this.getWorldBorder().setAbsoluteMaxSize(server.getAbsoluteMaxWorldSize()); - this.raids = this.getDataStorage().computeIfAbsent(Raids.factory(this), Raids.getFileId(this.dimensionTypeRegistration())); -@@ -681,7 +_,14 @@ - this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); - // Paper end - rewrite chunk system - this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit -- } -+ this.updateTickData(); // Folia - region threading - make sure it is initialised before ticked -+ } -+ -+ // Folia start - region threading -+ public void updateTickData() { -+ this.tickData = new io.papermc.paper.threadedregions.RegionizedServer.WorldLevelData(this, this.serverLevelData.getGameTime(), this.serverLevelData.getDayTime()); -+ } -+ // Folia end - region threading - - // Paper start - @Override -@@ -709,61 +_,39 @@ - return this.getChunkSource().getGenerator().getBiomeSource().getNoiseBiome(x, y, z, this.getChunkSource().randomState().sampler()); - } - -+ @Override // Folia - region threading - public StructureManager structureManager() { - return this.structureManager; - } - -- public void tick(BooleanSupplier hasTimeLeft) { -+ public void tick(BooleanSupplier hasTimeLeft, io.papermc.paper.threadedregions.TickRegions.TickRegionData region) { // Folia - regionised ticking -+ final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - regionised ticking - ProfilerFiller profilerFiller = Profiler.get(); -- this.handlingTick = true; -+ regionizedWorldData.setHandlingTick(true); // Folia - regionised ticking - TickRateManager tickRateManager = this.tickRateManager(); - boolean runsNormally = tickRateManager.runsNormally(); - if (runsNormally) { - profilerFiller.push("world border"); -- this.getWorldBorder().tick(); -+ //this.getWorldBorder().tick(); // Folia - regionised ticking - profilerFiller.popPush("weather"); -- this.advanceWeatherCycle(); -+ //this.advanceWeatherCycle(); // Folia - regionised ticking - profilerFiller.pop(); - } - -- int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); -- if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { -- // Paper start - create time skip event - move up calculations -- final long newDayTime = this.levelData.getDayTime() + 24000L; -- org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent( -- this.getWorld(), -- org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP, -- (newDayTime - newDayTime % 24000L) - this.getDayTime() -- ); -- // Paper end - create time skip event - move up calculations -- if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { -- // Paper start - call time skip event if gamerule is enabled -- // long l = this.levelData.getDayTime() + 24000L; // Paper - diff on change to above - newDayTime -- // this.setDayTime(l - l % 24000L); // Paper - diff on change to above - event param -- if (event.callEvent()) { -- this.setDayTime(this.getDayTime() + event.getSkipAmount()); -- } -- // Paper end - call time skip event if gamerule is enabled -- } -- -- if (!event.isCancelled()) this.wakeUpAllPlayers(); // Paper - only wake up players if time skip event is not cancelled -- if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) { -- this.resetWeatherCycle(); -- } -- } -- -- this.updateSkyBrightness(); -+ this.tickSleep(); // Folia - region threading - move into tickSleep -+ -+ //this.updateSkyBrightness(); // Folia - region threading - if (runsNormally) { - this.tickTime(); - } - - profilerFiller.push("tickPending"); - if (!this.isDebug() && runsNormally) { -- long l = this.getGameTime(); -+ long l = regionizedWorldData.getRedstoneGameTime(); // Folia - region threading - profilerFiller.push("blockTicks"); -- this.blockTicks.tick(l, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks -+ regionizedWorldData.getBlockLevelTicks().tick(l, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks // Folia - region ticking - profilerFiller.popPush("fluidTicks"); -- this.fluidTicks.tick(l, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks -+ regionizedWorldData.getFluidLevelTicks().tick(l, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks // Folia - region ticking - profilerFiller.pop(); - } - -@@ -779,9 +_,9 @@ - this.runBlockEvents(); - } - -- this.handlingTick = false; -+ regionizedWorldData.setHandlingTick(false); // Folia - regionised ticking - profilerFiller.pop(); -- boolean flag = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this -+ boolean flag = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this // Folia - unrestore this, we always need to tick empty worlds - if (flag) { - this.resetEmptyTime(); - } -@@ -789,19 +_,29 @@ - if (flag || this.emptyTime++ < 300) { - profilerFiller.push("entities"); - if (this.dragonFight != null && runsNormally) { -+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, this.dragonFight.origin)) { // Folia - region threading - profilerFiller.push("dragonFight"); - this.dragonFight.tick(); - profilerFiller.pop(); -+ } else { // Folia start - region threading -+ // try to load dragon fight -+ ChunkPos fightCenter = new ChunkPos(this.dragonFight.origin); -+ this.chunkSource.addTicketAtLevel( -+ TicketType.UNKNOWN, fightCenter, ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, -+ fightCenter -+ ); -+ } // Folia end - region threading - } - - io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR -- this.entityTickList -- .forEach( -+ regionizedWorldData // Folia - regionised ticking -+ .forEachTickingEntity( // Folia - regionised ticking - entity -> { - if (!entity.isRemoved()) { - if (!tickRateManager.isEntityFrozen(entity)) { - profilerFiller.push("checkDespawn"); - entity.checkDespawn(); -+ if (entity.isRemoved()) return; // Folia - region threading - if we despawned, DON'T TICK IT! - profilerFiller.pop(); - if (true) { // Paper - rewrite chunk system - Entity vehicle = entity.getVehicle(); -@@ -830,6 +_,36 @@ - profilerFiller.pop(); - } - -+ // Folia start - region threading -+ public void tickSleep() { -+ int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); -+ if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { -+ // Paper start - create time skip event - move up calculations -+ final long newDayTime = this.levelData.getDayTime() + 24000L; -+ org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent( -+ this.getWorld(), -+ org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP, -+ (newDayTime - newDayTime % 24000L) - this.getDayTime() -+ ); -+ // Paper end - create time skip event - move up calculations -+ if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { -+ // Paper start - call time skip event if gamerule is enabled -+ // long l = this.levelData.getDayTime() + 24000L; // Paper - diff on change to above - newDayTime -+ // this.setDayTime(l - l % 24000L); // Paper - diff on change to above - event param -+ if (event.callEvent()) { -+ this.setDayTime(this.getDayTime() + event.getSkipAmount()); -+ } -+ // Paper end - call time skip event if gamerule is enabled -+ } -+ -+ if (!event.isCancelled()) this.wakeUpAllPlayers(); // Paper - only wake up players if time skip event is not cancelled -+ if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) { -+ this.resetWeatherCycle(); -+ } -+ } -+ } -+ // Folia end - region threading -+ - @Override - public boolean shouldTickBlocksAt(long chunkPos) { - // Paper start - rewrite chunk system -@@ -840,12 +_,13 @@ - - protected void tickTime() { - if (this.tickTime) { -- long l = this.levelData.getGameTime() + 1L; -- this.serverLevelData.setGameTime(l); -+ io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - region threading -+ long l = regionizedWorldData.getRedstoneGameTime() + 1L; // Folia - region threading -+ regionizedWorldData.setRedstoneGameTime(l); // Folia - region threading - Profiler.get().push("scheduledFunctions"); -- this.serverLevelData.getScheduledEvents().tick(this.server, l); -+ //this.serverLevelData.getScheduledEvents().tick(this.server, l); // Folia - region threading - TODO any way to bring this in? - Profiler.get().pop(); -- if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { -+ if (false && this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { // Folia - region threading - this.setDayTime(this.levelData.getDayTime() + 1L); - } - } -@@ -863,16 +_,27 @@ - - private void wakeUpAllPlayers() { - this.sleepStatus.removeAllSleepers(); -- this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false)); -+ // Folia start - region threading -+ this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach((ServerPlayer entityplayer) -> { -+ // Folia start - region threading -+ entityplayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> { -+ if (player.level() != ServerLevel.this || !player.isSleeping()) { -+ return; -+ } -+ player.stopSleepInBed(false, false); -+ }, null, 1L); -+ } -+ ); -+ // Folia end - region threading - } - - // Paper start - optimise random ticking -- private final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); -+ private final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource.INSTANCE; // Folia - region threading - - private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) { - final LevelChunkSection[] sections = chunk.getSections(); - final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this); -- final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom; -+ final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = this.simpleRandom; // Folia - region threading - final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); - - final ChunkPos cpos = chunk.getPos(); -@@ -919,7 +_,7 @@ - // Paper end - optimise random ticking - - public void tickChunk(LevelChunk chunk, int randomTickSpeed) { -- final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom; // Paper - optimise random ticking -+ final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Folia - region threading - ChunkPos pos = chunk.getPos(); - boolean isRaining = this.isRaining(); - int minBlockX = pos.getMinBlockX(); -@@ -1044,7 +_,7 @@ - } - - public boolean isHandlingTick() { -- return this.handlingTick; -+ return this.getCurrentWorldData().isHandlingTick(); // Folia - regionised ticking - } - - public boolean canSleepThroughNights() { -@@ -1070,6 +_,14 @@ - } - - public void updateSleepingPlayerList() { -+ // Folia start - region threading -+ if (!io.papermc.paper.threadedregions.RegionizedServer.isGlobalTickThread()) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ ServerLevel.this.updateSleepingPlayerList(); -+ }); -+ return; -+ } -+ // Folia end - region threading - if (!this.players.isEmpty() && this.sleepStatus.update(this.players)) { - this.announceSleepStatus(); - } -@@ -1080,7 +_,7 @@ - return this.server.getScoreboard(); - } - -- private void advanceWeatherCycle() { -+ public void advanceWeatherCycle() { // Folia - region threading - public - boolean isRaining = this.isRaining(); - if (this.dimensionType().hasSkyLight()) { - if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE)) { -@@ -1166,7 +_,8 @@ - this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel)); - } - */ -- for (ServerPlayer player : this.players) { -+ ServerPlayer[] players = this.players.toArray(new ServerPlayer[0]); // Folia - region threading -+ for (ServerPlayer player : players) { // Folia - region threading - if (player.level() == this) { - player.tickWeather(); - } -@@ -1174,13 +_,13 @@ - - if (isRaining != this.isRaining()) { - // Only send weather packets to those affected -- for (ServerPlayer player : this.players) { -+ for (ServerPlayer player : players) { // Folia - region threading - if (player.level() == this) { - player.setPlayerWeather((!isRaining ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR), false); - } - } - } -- for (ServerPlayer player : this.players) { -+ for (ServerPlayer player : players) { // Folia - region threading - if (player.level() == this) { - player.updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel); - } -@@ -1241,13 +_,10 @@ - - // Paper start - log detailed entity tick information - // TODO replace with varhandle -- static final java.util.concurrent.atomic.AtomicReference currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>(); -+ // Folia - region threading - - public static List getCurrentlyTickingEntities() { -- Entity ticking = currentlyTickingEntity.get(); -- List ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking }); -- -- return ret; -+ throw new UnsupportedOperationException(); // Folia - region threading - } - // Paper end - log detailed entity tick information - -@@ -1255,9 +_,7 @@ - // Paper start - log detailed entity tick information - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); - try { -- if (currentlyTickingEntity.get() == null) { -- currentlyTickingEntity.lazySet(entity); -- } -+ // Folia - region threading - // Paper end - log detailed entity tick information - entity.setOldPosAndRot(); - ProfilerFiller profilerFiller = Profiler.get(); -@@ -1267,7 +_,16 @@ - final boolean isActive = io.papermc.paper.entity.activation.ActivationRange.checkIfActive(entity); // Paper - EAR 2 - if (isActive) { // Paper - EAR 2 - entity.tick(); -- entity.postTick(); // CraftBukkit -+ // Folia start - region threading -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) { -+ // removed from region while ticking -+ return; -+ } -+ if (entity.handlePortal()) { -+ // portalled -+ return; -+ } -+ // Folia end - region threading - } else {entity.inactiveTick();} // Paper - EAR 2 - profilerFiller.pop(); - -@@ -1276,9 +_,7 @@ - } - // Paper start - log detailed entity tick information - } finally { -- if (currentlyTickingEntity.get() == entity) { -- currentlyTickingEntity.lazySet(null); -- } -+ // Folia - region threading - } - // Paper end - log detailed entity tick information - } -@@ -1286,7 +_,7 @@ - private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2 - if (passengerEntity.isRemoved() || passengerEntity.getVehicle() != ridingEntity) { - passengerEntity.stopRiding(); -- } else if (passengerEntity instanceof Player || this.entityTickList.contains(passengerEntity)) { -+ } else if (passengerEntity instanceof Player || this.getCurrentWorldData().hasEntityTickingEntity(passengerEntity)) { // Folia - region threading - passengerEntity.setOldPosAndRot(); - passengerEntity.tickCount++; - ProfilerFiller profilerFiller = Profiler.get(); -@@ -1295,7 +_,16 @@ - // Paper start - EAR 2 - if (isActive) { - passengerEntity.rideTick(); -- passengerEntity.postTick(); // CraftBukkit -+ // Folia start - region threading -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(passengerEntity)) { -+ // removed from region while ticking -+ return; -+ } -+ if (passengerEntity.handlePortal()) { -+ // portalled -+ return; -+ } -+ // Folia end - region threading - } else { - passengerEntity.setDeltaMovement(Vec3.ZERO); - passengerEntity.inactiveTick(); -@@ -1369,19 +_,20 @@ - } - // Paper end - add close param - -- // CraftBukkit start - moved from MinecraftServer.saveChunks -+ // Folia - move into saveLevelData -+ } -+ -+ public void saveLevelData(boolean join) { // Folia - public -+ if (this.dragonFight != null) { -+ this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit -+ } -+ // Folia start - moved into saveLevelData - ServerLevel worldserver1 = this; - - this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); - this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess())); - this.levelStorageAccess.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); -- // CraftBukkit end -- } -- -- private void saveLevelData(boolean join) { -- if (this.dragonFight != null) { -- this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit -- } -+ // Folia end - moved into saveLevelData - - DimensionDataStorage dataStorage = this.getChunkSource().getDataStorage(); - if (join) { -@@ -1437,6 +_,19 @@ - return list; - } - -+ // Folia start - region threading -+ @Nullable -+ public ServerPlayer getRandomLocalPlayer() { -+ List list = this.getLocalPlayers(); -+ list = new java.util.ArrayList<>(list); -+ list.removeIf((ServerPlayer player) -> { -+ return !player.isAlive(); -+ }); -+ -+ return list.isEmpty() ? null : (ServerPlayer) list.get(this.random.nextInt(list.size())); -+ } -+ // Folia end - region threading -+ - @Nullable - public ServerPlayer getRandomPlayer() { - List players = this.getPlayers(LivingEntity::isAlive); -@@ -1518,8 +_,8 @@ - } else { - if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added - // Paper start - capture all item additions to the world -- if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { -- captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); -+ if (this.getCurrentWorldData().captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { // Folia - region threading -+ this.getCurrentWorldData().captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); // Folia - region threading - return true; - } - // Paper end - capture all item additions to the world -@@ -1694,13 +_,14 @@ - - @Override - public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { -- if (this.isUpdatingNavigations) { -+ final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - region threading -+ if (false && this.isUpdatingNavigations) { // Folia - region threading - String string = "recursive call to sendBlockUpdated"; - Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); - } - - this.getChunkSource().blockChanged(pos); -- this.pathTypesByPosCache.invalidate(pos); -+ regionizedWorldData.pathTypesByPosCache.invalidate(pos); // Folia - region threading - if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates - VoxelShape collisionShape = oldState.getCollisionShape(this, pos); - VoxelShape collisionShape1 = newState.getCollisionShape(this, pos); -@@ -1708,7 +_,8 @@ - List list = new ObjectArrayList<>(); - - try { // Paper - catch CME see below why -- for (Mob mob : this.navigatingMobs) { -+ for (java.util.Iterator iterator = regionizedWorldData.getNavigatingMobs(); iterator.hasNext();) { // Folia - region threading -+ Mob mob = iterator.next(); // Folia - region threading - PathNavigation navigation = mob.getNavigation(); - if (navigation.shouldRecomputePath(pos)) { - list.add(navigation); -@@ -1725,13 +_,13 @@ - // Paper end - catch CME see below why - - try { -- this.isUpdatingNavigations = true; -+ //this.isUpdatingNavigations = true; // Folia - region threading - - for (PathNavigation pathNavigation : list) { - pathNavigation.recomputePath(); - } - } finally { -- this.isUpdatingNavigations = false; -+ //this.isUpdatingNavigations = false; // Folia - region threading - } - } - } // Paper - option to disable pathfinding updates -@@ -1739,29 +_,29 @@ - - @Override - public void updateNeighborsAt(BlockPos pos, Block block) { -- if (captureBlockStates) { return; } // Paper - Cancel all physics during placement -+ if (this.getCurrentWorldData().captureBlockStates) { return; } // Paper - Cancel all physics during placement // Folia - region threading - this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, null, null)); - } - - @Override - public void updateNeighborsAt(BlockPos pos, Block block, @Nullable Orientation orientation) { -- if (captureBlockStates) { return; } // Paper - Cancel all physics during placement -- this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); -+ if (this.getCurrentWorldData().captureBlockStates) { return; } // Paper - Cancel all physics during placement // Folia - region threading -+ this.getCurrentWorldData().neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); // Folia - region threading - } - - @Override - public void updateNeighborsAtExceptFromFacing(BlockPos pos, Block block, Direction facing, @Nullable Orientation orientation) { -- this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, facing, orientation); -+ this.getCurrentWorldData().neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, facing, orientation); // Folia - region threading - } - - @Override - public void neighborChanged(BlockPos pos, Block block, @Nullable Orientation orientation) { -- this.neighborUpdater.neighborChanged(pos, block, orientation); -+ this.getCurrentWorldData().neighborUpdater.neighborChanged(pos, block, orientation); // Folia - region threading - } - - @Override - public void neighborChanged(BlockState state, BlockPos pos, Block block, @Nullable Orientation orientation, boolean movedByPiston) { -- this.neighborUpdater.neighborChanged(state, pos, block, orientation, movedByPiston); -+ this.getCurrentWorldData().neighborUpdater.neighborChanged(state, pos, block, orientation, movedByPiston); // Folia - region threading - } - - @Override -@@ -1851,7 +_,7 @@ - // CraftBukkit end - ParticleOptions particleOptions = serverExplosion.isSmall() ? smallExplosionParticles : largeExplosionParticles; - -- for (ServerPlayer serverPlayer : this.players) { -+ for (ServerPlayer serverPlayer : this.getLocalPlayers()) { // Folia - region thraeding - if (serverPlayer.distanceToSqr(vec3) < 4096.0) { - Optional optional = Optional.ofNullable(serverExplosion.getHitPlayers().get(serverPlayer)); - serverPlayer.connection.send(new ClientboundExplodePacket(vec3, optional, particleOptions, explosionSound)); -@@ -1867,14 +_,17 @@ - - @Override - public void blockEvent(BlockPos pos, Block block, int eventID, int eventParam) { -- this.blockEvents.add(new BlockEventData(pos, block, eventID, eventParam)); -+ this.getCurrentWorldData().pushBlockEvent(new BlockEventData(pos, block, eventID, eventParam)); // Folia - regionised ticking - } - - private void runBlockEvents() { -- this.blockEventsToReschedule.clear(); -+ List blockEventsToReschedule = new ArrayList<>(64); // Folia - regionised ticking - -- while (!this.blockEvents.isEmpty()) { -- BlockEventData blockEventData = this.blockEvents.removeFirst(); -+ // Folia start - regionised ticking -+ io.papermc.paper.threadedregions.RegionizedWorldData worldRegionData = this.getCurrentWorldData(); -+ BlockEventData blockEventData; -+ while ((blockEventData = worldRegionData.removeFirstBlockEvent()) != null) { -+ // Folia end - regionised ticking - if (this.shouldTickBlocksAt(blockEventData.pos())) { - if (this.doBlockEvent(blockEventData)) { - this.server -@@ -1890,11 +_,11 @@ - ); - } - } else { -- this.blockEventsToReschedule.add(blockEventData); -+ blockEventsToReschedule.add(blockEventData); // Folia - regionised ticking - } - } - -- this.blockEvents.addAll(this.blockEventsToReschedule); -+ worldRegionData.pushBlockEvents(blockEventsToReschedule); // Folia - regionised ticking - } - - private boolean doBlockEvent(BlockEventData event) { -@@ -1904,12 +_,12 @@ - - @Override - public LevelTicks getBlockTicks() { -- return this.blockTicks; -+ return this.getCurrentWorldData().getBlockLevelTicks(); // Folia - region ticking - } - - @Override - public LevelTicks getFluidTicks() { -- return this.fluidTicks; -+ return this.getCurrentWorldData().getFluidLevelTicks(); // Folia - region ticking - } - - @Nonnull -@@ -1962,7 +_,7 @@ - double zOffset, - double speed - ) { -- return sendParticlesSource(this.players, sender, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); -+ return sendParticlesSource(this.getLocalPlayers(), sender, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); // Folia - region threading - } - public int sendParticlesSource( - List receivers, -@@ -2045,12 +_,12 @@ - @Nullable - public Entity getEntityOrPart(int id) { - Entity entity = this.getEntities().get(id); -- return entity != null ? entity : this.dragonParts.get(id); -+ return entity != null ? entity : this.dragonParts.get((long)id); // Folia - diff on change - } - - @Override - public Collection dragonParts() { -- return this.dragonParts.values(); -+ return this.dragonParts.values(); // Folia - diff on change - } - - @Nullable -@@ -2105,6 +_,7 @@ - // Paper start - Call missing map initialize event and set id - final DimensionDataStorage storage = this.getServer().overworld().getDataStorage(); - -+ synchronized (storage.cache) { // Folia - region threading - final Optional cacheEntry = storage.cache.get(mapId.key()); - if (cacheEntry == null) { // Cache did not contain, try to load and may init - final MapItemSavedData mapData = storage.get(MapItemSavedData.factory(), mapId.key()); // get populates the cache -@@ -2124,6 +_,7 @@ - } - - return null; -+ } // Folia - region threading - // Paper end - Call missing map initialize event and set id - } - -@@ -2178,6 +_,7 @@ - } - - public boolean setChunkForced(int chunkX, int chunkZ, boolean add) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify force loaded chunks off of the global region"); // Folia - region threading - ForcedChunksSavedData forcedChunksSavedData = this.getDataStorage().computeIfAbsent(ForcedChunksSavedData.factory(), "chunks"); - ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); - long packedChunkPos = chunkPos.toLong(); -@@ -2185,7 +_,7 @@ - if (add) { - flag = forcedChunksSavedData.getChunks().add(packedChunkPos); - if (flag) { -- this.getChunk(chunkX, chunkZ); -+ //this.getChunk(chunkX, chunkZ); // Folia - region threading - we must let the chunk load asynchronously - } - } else { - flag = forcedChunksSavedData.getChunks().remove(packedChunkPos); -@@ -2210,11 +_,24 @@ - Optional> optional1 = PoiTypes.forState(newState); - if (!Objects.equals(optional, optional1)) { - BlockPos blockPos = pos.immutable(); -- optional.ifPresent(poiType -> this.getServer().execute(() -> { -+ // Folia start - region threading -+ optional.ifPresent(poiType -> { -+ Runnable run = () -> { -+ // Folia end - region threading - this.getPoiManager().remove(blockPos); - DebugPackets.sendPoiRemovedPacket(this, blockPos); -- })); -- optional1.ifPresent(poiType -> this.getServer().execute(() -> { -+ // Folia start - region threading -+ }; -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask( -+ this, blockPos.getX() >> 4, blockPos.getZ() >> 4, run -+ ); -+ }); -+ // Folia end - region threading -+ // Folia start - region threading -+ optional1.ifPresent(poiType -> { -+ Runnable run = () -> { -+ // Folia end - region threading - // Paper start - Remove stale POIs - if (optional.isEmpty() && this.getPoiManager().exists(blockPos, ignored -> true)) { - this.getPoiManager().remove(blockPos); -@@ -2222,7 +_,15 @@ - // Paper end - Remove stale POIs - this.getPoiManager().add(blockPos, (Holder)poiType); - DebugPackets.sendPoiAddedPacket(this, blockPos); -- })); -+ // Folia start - region threading -+ }; -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask( -+ this, blockPos.getX() >> 4, blockPos.getZ() >> 4, run -+ ); -+ // Folia end - region threading -+ }); -+ // Folia end - region threading - } - } - -@@ -2276,7 +_,7 @@ - } - - bufferedWriter.write(String.format(Locale.ROOT, "entities: %s\n", this.moonrise$getEntityLookup().getDebugInfo())); // Paper - rewrite chunk system -- bufferedWriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); -+ //bufferedWriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); // Folia - region threading - bufferedWriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count())); - bufferedWriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count())); - bufferedWriter.write("distance_manager: " + chunkMap.getDistanceManager().getDebugStatus() + "\n"); -@@ -2346,7 +_,7 @@ - private void dumpBlockEntityTickers(Writer output) throws IOException { - CsvOutput csvOutput = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("type").build(output); - -- for (TickingBlockEntity tickingBlockEntity : this.blockEntityTickers) { -+ for (TickingBlockEntity tickingBlockEntity : (Iterable)null) { // Folia - region threading - BlockPos pos = tickingBlockEntity.getPos(); - csvOutput.writeRow(pos.getX(), pos.getY(), pos.getZ(), tickingBlockEntity.getType()); - } -@@ -2354,14 +_,14 @@ - - @VisibleForTesting - public void clearBlockEvents(BoundingBox boundingBox) { -- this.blockEvents.removeIf(blockEventData -> boundingBox.isInside(blockEventData.pos())); -+ this.getCurrentWorldData().removeIfBlockEvents(blockEventData -> boundingBox.isInside(blockEventData.pos())); // Folia - regionised ticking - } - - @Override - public void blockUpdated(BlockPos pos, Block block) { - if (!this.isDebug()) { - // CraftBukkit start -- if (this.populating) { -+ if (this.getCurrentWorldData().populating) { // Folia - region threading - return; - } - // CraftBukkit end -@@ -2410,8 +_,8 @@ - this.players.size(), - this.moonrise$getEntityLookup().getDebugInfo(), // Paper - rewrite chunk system - getTypeCount(this.moonrise$getEntityLookup().getAll(), entity -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()), // Paper - rewrite chunk system -- this.blockEntityTickers.size(), -- getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType), -+ 0, // Folia - region threading -+ "null", // Folia - region threading - this.getBlockTicks().count(), - this.getFluidTicks().count(), - this.gatherChunkSourceStats() -@@ -2463,15 +_,15 @@ - } - - public void startTickingChunk(LevelChunk chunk) { -- chunk.unpackTicks(this.getLevelData().getGameTime()); -+ chunk.unpackTicks(this.getRedstoneGameTime()); // Folia - region threading - } - - public void onStructureStartsAvailable(ChunkAccess chunk) { -- this.server.execute(() -> this.structureCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts())); -+ this.structureCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts()); // Folia - region threading - } - - public PathTypeCache getPathTypeCache() { -- return this.pathTypesByPosCache; -+ return this.getCurrentWorldData().pathTypesByPosCache; // Folia - region threading - } - - @Override -@@ -2489,7 +_,7 @@ - return this.moonrise$getAnyChunkIfLoaded(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkPos)) != null; // Paper - rewrite chunk system - } - -- private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { -+ public boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { // Folia - region threaded - make public - // Paper start - rewrite chunk system - final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos); - // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded -@@ -2581,7 +_,7 @@ - // Paper start - optimize redstone (Alternate Current) - @Override - public alternate.current.wire.WireHandler getWireHandler() { -- return wireHandler; -+ return this.getCurrentWorldData().wireHandler; // Folia - region threading - } - // Paper end - optimize redstone (Alternate Current) - -@@ -2592,18 +_,18 @@ - - @Override - public void onDestroyed(Entity entity) { -- ServerLevel.this.getScoreboard().entityRemoved(entity); -+ // ServerLevel.this.getScoreboard().entityRemoved(entity); // Folia - region threading - } - - @Override - public void onTickingStart(Entity entity) { - if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking -- ServerLevel.this.entityTickList.add(entity); -+ ServerLevel.this.getCurrentWorldData().addEntityTickingEntity(entity); // Folia - region threading - } - - @Override - public void onTickingEnd(Entity entity) { -- ServerLevel.this.entityTickList.remove(entity); -+ ServerLevel.this.getCurrentWorldData().removeEntityTickingEntity(entity); // Folia - region threading - // Paper start - Reset pearls when they stop being ticked - if (ServerLevel.this.paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && ServerLevel.this.paperConfig().misc.legacyEnderPearlBehavior && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { - pearl.cachedOwner = null; -@@ -2615,6 +_,7 @@ - @Override - public void onTrackingStart(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot -+ ServerLevel.this.getCurrentWorldData().addLoadedEntity(entity); // Folia - region threading - // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true - if (entity instanceof ServerPlayer serverPlayer) { - ServerLevel.this.players.add(serverPlayer); -@@ -2629,12 +_,12 @@ - ); - } - -- ServerLevel.this.navigatingMobs.add(mob); -+ ServerLevel.this.getCurrentWorldData().addNavigatingMob(mob); // Folia - region threading - } - - if (entity instanceof EnderDragon enderDragon) { - for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) { -- ServerLevel.this.dragonParts.put(enderDragonPart.getId(), enderDragonPart); -+ ServerLevel.this.dragonParts.put((long)enderDragonPart.getId(), enderDragonPart); // Folia - diff on change - } - } - -@@ -2657,18 +_,27 @@ - @Override - public void onTrackingEnd(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot -+ ServerLevel.this.getCurrentWorldData().removeLoadedEntity(entity); // Folia - region threading - // Spigot start // TODO I don't think this is needed anymore - if (entity instanceof Player player) { - for (final ServerLevel level : ServerLevel.this.getServer().getAllLevels()) { -- for (final Optional savedData : level.getDataStorage().cache.values()) { -+ // Folia start - make map data thread-safe -+ List> worldDataCache; -+ synchronized (level.getDataStorage().cache) { -+ worldDataCache = new java.util.ArrayList<>(level.getDataStorage().cache.values()); -+ } -+ for (final Optional savedData : worldDataCache) { -+ // Folia end - make map data thread-safe - if (savedData.isEmpty() || !(savedData.get() instanceof MapItemSavedData map)) { - continue; - } - -+ synchronized (map) { // Folia - make map data thread-safe - map.carriedByPlayers.remove(player); - if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) { - map.decorations.remove(player.getName().getString()); - } -+ } // Folia - make map data thread-safe - } - } - } -@@ -2699,18 +_,19 @@ - ); - } - -- ServerLevel.this.navigatingMobs.remove(mob); -+ ServerLevel.this.getCurrentWorldData().removeNavigatingMob(mob); // Folia - region threading - } - - if (entity instanceof EnderDragon enderDragon) { - for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) { -- ServerLevel.this.dragonParts.remove(enderDragonPart.getId()); -+ ServerLevel.this.dragonParts.remove((long)enderDragonPart.getId()); // Folia - diff on change - } - } - - entity.updateDynamicGameEventListener(DynamicGameEventListener::remove); - // CraftBukkit start - entity.valid = false; -+ // Folia - region threading - TODO THIS SHIT - if (!(entity instanceof ServerPlayer)) { - for (ServerPlayer player : ServerLevel.this.server.getPlayerList().players) { // Paper - call onEntityRemove for all online players - player.getBukkitEntity().onEntityRemove(entity); -@@ -2738,11 +_,11 @@ - private long lagCompensationTick = MinecraftServer.SERVER_INIT; - - public long getLagCompensationTick() { -- return this.lagCompensationTick; -+ return this.getCurrentWorldData().getLagCompensationTick(); // Folia - region threading - } - - public void updateLagCompensationTick() { -- this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L)); -+ throw new UnsupportedOperationException(); // Folia - region threading - } - // Paper end - lag compensation - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch deleted file mode 100644 index fafc5dd..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ /dev/null @@ -1,631 +0,0 @@ ---- a/net/minecraft/server/level/ServerPlayer.java -+++ b/net/minecraft/server/level/ServerPlayer.java -@@ -180,7 +_,7 @@ - - public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system - private static final Logger LOGGER = LogUtils.getLogger(); -- public long lastSave = MinecraftServer.currentTick; // Paper - Incremental chunk and player saving -+ public static final long LAST_SAVE_ABSENT = Long.MIN_VALUE; public long lastSave = LAST_SAVE_ABSENT; // Paper // Folia - threaded regions - changed to nanoTime - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; - private static final int FLY_STAT_RECORDING_SPEED = 25; -@@ -443,8 +_,149 @@ - this.maxHealthCache = this.getMaxHealth(); - } - -+ // Folia start - region threading -+ private static final int SPAWN_RADIUS_SELECTION_SEARCH = 5; -+ -+ private static BlockPos getRandomSpawn(ServerLevel world, RandomSource random) { -+ BlockPos spawn = world.getSharedSpawnPos(); -+ double radius = (double)Math.max(0, world.getGameRules().getInt(GameRules.RULE_SPAWN_RADIUS)); -+ -+ double spawnX = (double)spawn.getX() + 0.5; -+ double spawnZ = (double)spawn.getZ() + 0.5; -+ -+ net.minecraft.world.level.border.WorldBorder worldBorder = world.getWorldBorder(); -+ -+ double selectMinX = Math.max(worldBorder.getMinX() + 1.0, spawnX - radius); -+ double selectMinZ = Math.max(worldBorder.getMinZ() + 1.0, spawnZ - radius); -+ double selectMaxX = Math.min(worldBorder.getMaxX() - 1.0, spawnX + radius); -+ double selectMaxZ = Math.min(worldBorder.getMaxZ() - 1.0, spawnZ + radius); -+ -+ double amountX = selectMaxX - selectMinX; -+ double amountZ = selectMaxZ - selectMinZ; -+ -+ int selectX = amountX < 1.0 ? Mth.floor(worldBorder.getCenterX()) : (int)Mth.floor((amountX + 1.0) * random.nextDouble() + selectMinX); -+ int selectZ = amountZ < 1.0 ? Mth.floor(worldBorder.getCenterZ()) : (int)Mth.floor((amountZ + 1.0) * random.nextDouble() + selectMinZ); -+ -+ return new BlockPos(selectX, 0, selectZ); -+ } -+ -+ private static void completeSpawn(ServerLevel world, BlockPos selected, -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable 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 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 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 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 toComplete) { // Folia - region threading -+ BlockPos blockposition = world.getSharedSpawnPos(); -+ -+ if (world.dimensionType().hasSkyLight() && world.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit -+ selectSpawn(world, player, player.random, new int[1], 500, toComplete); -+ } else { -+ selectSpawnWithoutRadius(world, player, blockposition, toComplete); -+ } -+ -+ } -+ // Folia end - region threading -+ - @Override - public BlockPos adjustSpawnLocation(ServerLevel level, BlockPos pos) { -+ // Folia start - region threading -+ if (true) { -+ throw new UnsupportedOperationException(); -+ } -+ // Folia end - region threading - AABB aabb = this.getDimensions(Pose.STANDING).makeBoundingBox(Vec3.ZERO); - BlockPos blockPos = pos; - if (level.dimensionType().hasSkyLight() && level.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit -@@ -533,7 +_,7 @@ - this.getBukkitEntity().readExtraData(compound); // CraftBukkit - - if (this.isSleeping()) { -- this.stopSleeping(); -+ this.stopSleepingRaw(); // Folia - do not modify or read worldstate during data deserialization - } - - // CraftBukkit start -@@ -709,10 +_,17 @@ - ServerLevel level = this.level().getServer().getLevel(optional.get()); - if (level != null) { - Entity entity = EntityType.loadEntityRecursive( -- compoundTag, level, EntitySpawnReason.LOAD, entity1 -> !level.addWithUUID(entity1) ? null : entity1 -+ compoundTag, level, EntitySpawnReason.LOAD, entity1 -> entity1 // Folia - region threading - delay world add - ); - if (entity != null) { -- placeEnderPearlTicket(level, entity.chunkPosition()); -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( -+ level, entity.chunkPosition().x, entity.chunkPosition().z, () -> { -+ level.addFreshEntityWithPassengers(entity); -+ ServerPlayer.placeEnderPearlTicket(level, entity.chunkPosition()); -+ } -+ ); -+ // Folia end - region threading - } else { - LOGGER.warn("Failed to spawn player ender pearl in level ({}), skipping", optional.get()); - } -@@ -817,12 +_,23 @@ - - Entity camera = this.getCamera(); - if (camera != this) { -- if (camera.isAlive()) { -+ if (camera.canBeSpectated()) { // Folia - region threading - replace removed check -+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(camera) && !camera.isRemoved()) { // Folia - region threading - this.absMoveTo(camera.getX(), camera.getY(), camera.getZ(), camera.getYRot(), camera.getXRot()); - this.serverLevel().getChunkSource().move(this); - if (this.wantsToStopRiding()) { - this.setCamera(this); - } -+ } else { // Folia start - region threading -+ Entity realCamera = camera.getBukkitEntity().getHandleRaw(); -+ if (realCamera != camera) { -+ this.setCamera(this); -+ this.setCamera(realCamera); -+ } else { -+ this.teleportToCameraOffRegion(); -+ } -+ } -+ // Folia end - region threading - } else { - this.setCamera(this); - } -@@ -1357,9 +_,332 @@ - } - } - -+ // Folia start - region threading -+ /** -+ * Teleport flag indicating that the player is to be respawned, expected to only be used -+ * internally for {@link #respawn(java.util.function.Consumer, PlayerRespawnEvent.RespawnReason)} -+ */ -+ public static final long TELEPORT_FLAGS_PLAYER_RESPAWN = Long.MIN_VALUE >>> 0; -+ -+ public void exitEndCredits() { -+ if (!this.wonGame) { -+ // not in the end credits anymore -+ return; -+ } -+ this.wonGame = false; -+ -+ this.respawn((player) -> { -+ CriteriaTriggers.CHANGED_DIMENSION.trigger(player, Level.END, Level.OVERWORLD); -+ }, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL, true); -+ } -+ -+ public void respawn(java.util.function.Consumer respawnComplete, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason reason) { -+ this.respawn(respawnComplete, reason, false); -+ } -+ -+ private void respawn(java.util.function.Consumer 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 spawnPosComplete = -+ new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); -+ boolean[] usedRespawnAnchor = new boolean[1]; -+ -+ // set up post spawn location logic -+ spawnPosComplete.addWaiter((spawnLoc, throwable) -> { -+ // update pos and velocity -+ ServerPlayer.this.setPosRaw(spawnLoc.getX(), spawnLoc.getY(), spawnLoc.getZ()); -+ ServerPlayer.this.setYRot(spawnLoc.getYaw()); -+ ServerPlayer.this.setYHeadRot(spawnLoc.getYaw()); -+ ServerPlayer.this.setXRot(spawnLoc.getPitch()); -+ ServerPlayer.this.setDeltaMovement(Vec3.ZERO); -+ // placeInAsync will update the world -+ -+ this.placeInAsync( -+ origin, -+ // use the load chunk flag just in case the spawn loc isn't loaded, and to ensure the chunks -+ // stay loaded for a bit with the teleport ticket -+ ((org.bukkit.craftbukkit.CraftWorld)spawnLoc.getWorld()).getHandle(), -+ TELEPORT_FLAG_LOAD_CHUNK | TELEPORT_FLAGS_PLAYER_RESPAWN, -+ passengerTree, // note: we expect this to just be the player, no passengers -+ (entity) -> { -+ // now the player is in the world, and can receive sound -+ if (usedRespawnAnchor[0]) { -+ ServerPlayer.this.connection.send( -+ new ClientboundSoundPacket( -+ net.minecraft.sounds.SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, -+ ServerPlayer.this.getX(), ServerPlayer.this.getY(), ServerPlayer.this.getZ(), -+ 1.0F, 1.0F, ServerPlayer.this.serverLevel().getRandom().nextLong() -+ ) -+ ); -+ } -+ // now the respawn logic is complete -+ -+ // last, call the function callback -+ if (respawnComplete != null) { -+ respawnComplete.accept(ServerPlayer.this); -+ } -+ } -+ ); -+ }); -+ -+ // find and modify respawn block state -+ if (respawnWorld == null || respawnPos == null) { -+ // default to regular spawn -+ fudgeSpawnLocation(this.server.getLevel(Level.OVERWORLD), this, spawnPosComplete); -+ } else { -+ // load chunk for block -+ // give at least 1 radius of loaded chunks so that we do not sync load anything -+ int radiusBlocks = 16; -+ respawnWorld.moonrise$loadChunksAsync(respawnPos, radiusBlocks, -+ ca.spottedleaf.concurrentutil.util.Priority.HIGHER, -+ (chunks) -> { -+ ServerPlayer.RespawnPosAngle spawnPos = ServerPlayer.findRespawnAndUseSpawnBlock( -+ respawnWorld, respawnPos, respawnAngle, isRespawnForced, !alive -+ ).orElse(null); -+ if (spawnPos == null) { -+ // no spawn -+ ServerPlayer.this.connection.send( -+ new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F) -+ ); -+ ServerPlayer.this.setRespawnPosition( -+ null, null, 0f, false, false, -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN -+ ); -+ // default to regular spawn -+ fudgeSpawnLocation(this.server.getLevel(Level.OVERWORLD), this, spawnPosComplete); -+ return; -+ } -+ -+ boolean isRespawnAnchor = respawnWorld.getBlockState(respawnPos).is(net.minecraft.world.level.block.Blocks.RESPAWN_ANCHOR); -+ boolean isBed = respawnWorld.getBlockState(respawnPos).is(net.minecraft.tags.BlockTags.BEDS); -+ usedRespawnAnchor[0] = !alive && isRespawnAnchor; -+ -+ ServerPlayer.this.setRespawnPosition( -+ respawnWorld.dimension(), respawnPos, respawnAngle, isRespawnForced, false, -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN -+ ); -+ -+ // finished now, pass the location on -+ spawnPosComplete.complete( -+ io.papermc.paper.util.MCUtil.toLocation(respawnWorld, spawnPos.position(), spawnPos.yaw(), 0.0f) -+ ); -+ return; -+ } -+ ); -+ } -+ } -+ -+ @Override -+ protected void teleportSyncSameRegion(Vec3 pos, Float yaw, Float pitch, Vec3 velocity) { -+ if (yaw != null) { -+ this.setYRot(yaw.floatValue()); -+ this.setYHeadRot(yaw.floatValue()); -+ } -+ if (pitch != null) { -+ this.setXRot(pitch.floatValue()); -+ } -+ if (velocity != null) { -+ this.setDeltaMovement(velocity); -+ } -+ this.connection.internalTeleport( -+ new net.minecraft.world.entity.PositionMoveRotation( -+ pos, this.getDeltaMovement(), this.getYRot(), this.getXRot() -+ ), -+ java.util.Collections.emptySet() -+ ); -+ this.connection.resetPosition(); -+ this.setOldPosAndRot(); -+ this.resetStoredPositions(); -+ } -+ -+ @Override -+ protected ServerPlayer transformForAsyncTeleport(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity) { -+ // must be manually removed from connections -+ this.serverLevel().getCurrentWorldData().connections.remove(this.connection.connection); -+ this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); -+ -+ this.spawnIn(destination); -+ this.transform(pos, yaw, pitch, velocity); -+ -+ return this; -+ } -+ -+ @Override -+ public void preChangeDimension() { -+ super.preChangeDimension(); -+ this.stopUsingItem(); -+ } -+ -+ @Override -+ protected void placeSingleSync(ServerLevel originWorld, ServerLevel destination, EntityTreeNode treeNode, long teleportFlags) { -+ if (destination == originWorld && (teleportFlags & TELEPORT_FLAGS_PLAYER_RESPAWN) == 0L) { -+ this.unsetRemoved(); -+ destination.addDuringTeleport(this); -+ -+ // must be manually added to connections -+ this.serverLevel().getCurrentWorldData().connections.add(this.connection.connection); -+ -+ // required to set up the pending teleport stuff to the client, and to actually update -+ // the player's position clientside -+ this.connection.internalTeleport( -+ new net.minecraft.world.entity.PositionMoveRotation( -+ this.position(), this.getDeltaMovement(), this.getYRot(), this.getXRot() -+ ), -+ java.util.Collections.emptySet() -+ ); -+ this.connection.resetPosition(); -+ -+ this.postChangeDimension(); -+ } else { -+ // Modelled after PlayerList#respawn -+ -+ // We avoid checking for disconnection here, which means we do not have to add/remove from -+ // the player list here. We can let this be properly handled by the connection handler -+ -+ // pre-add logic -+ PlayerList playerlist = this.server.getPlayerList(); -+ net.minecraft.world.level.storage.LevelData worlddata = destination.getLevelData(); -+ this.connection.send( -+ new ClientboundRespawnPacket( -+ this.createCommonSpawnInfo(destination), -+ (teleportFlags & TELEPORT_FLAGS_PLAYER_RESPAWN) == 0L ? (byte)1 : (byte)0 -+ ) -+ ); -+ // don't bother with the chunk cache radius and simulation distance packets, they are handled -+ // by the chunk loader -+ this.spawnIn(destination); // important that destination != null -+ // we can delay teleport until later, the player position is already set up at the target -+ this.setShiftKeyDown(false); -+ -+ this.connection.send(new net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket( -+ destination.getSharedSpawnPos(), destination.getSharedSpawnAngle() -+ )); -+ this.connection.send(new ClientboundChangeDifficultyPacket( -+ worlddata.getDifficulty(), worlddata.isDifficultyLocked() -+ )); -+ this.connection.send(new ClientboundSetExperiencePacket( -+ this.experienceProgress, this.totalExperience, this.experienceLevel -+ )); -+ -+ playerlist.sendActivePlayerEffects(this); -+ playerlist.sendLevelInfo(this, destination); -+ playerlist.sendPlayerPermissionLevel(this); -+ -+ // regular world add logic -+ this.unsetRemoved(); -+ destination.addDuringTeleport(this); -+ -+ // must be manually added to connections -+ this.serverLevel().getCurrentWorldData().connections.add(this.connection.connection); -+ -+ // required to set up the pending teleport stuff to the client, and to actually update -+ // the player's position clientside -+ this.connection.internalTeleport( -+ new net.minecraft.world.entity.PositionMoveRotation( -+ this.position(), this.getDeltaMovement(), this.getYRot(), this.getXRot() -+ ), -+ java.util.Collections.emptySet() -+ ); -+ this.connection.resetPosition(); -+ -+ // delay callback until after post add logic -+ -+ // post add logic -+ -+ // "Added from changeDimension" -+ this.setHealth(this.getHealth()); -+ playerlist.sendAllPlayerInfo(this); -+ this.onUpdateAbilities(); -+ /*for (MobEffectInstance mobEffect : this.getActiveEffects()) { -+ this.connection.send(new ClientboundUpdateMobEffectPacket(this.getId(), mobEffect, false)); -+ }*/ // handled by sendActivePlayerEffects -+ -+ // Paper start - Reset shield blocking on dimension change -+ if (this.isBlocking()) { -+ this.stopUsingItem(); -+ } -+ // Paper end - Reset shield blocking on dimension change -+ -+ this.triggerDimensionChangeTriggers(originWorld); -+ -+ // finished -+ -+ this.postChangeDimension(); -+ } -+ } -+ -+ @Override -+ public boolean endPortalLogicAsync(BlockPos portalPos) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async"); -+ -+ if (this.level().getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) { -+ if (!this.canPortalAsync(null, false)) { -+ return false; -+ } -+ this.wonGame = true; -+ // TODO is there a better solution to this that DOESN'T skip the credits? -+ this.seenCredits = true; -+ if (!this.seenCredits) { -+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 0.0F)); -+ } -+ this.exitEndCredits(); -+ return true; -+ } else { -+ return super.endPortalLogicAsync(portalPos); -+ } -+ } -+ -+ @Override -+ protected void prePortalLogic(ServerLevel origin, ServerLevel destination, PortalType type) { -+ super.prePortalLogic(origin, destination, type); -+ if (origin.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.OVERWORLD && destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER) { -+ this.enteredNetherPosition = this.position(); -+ } -+ } -+ // Folia end - region threading -+ - @Nullable - @Override - public ServerPlayer teleport(TeleportTransition teleportTransition) { -+ // Folia start - region threading -+ if (true) { -+ throw new UnsupportedOperationException("Must use teleportAsync while in region threading"); -+ } -+ // Folia end - region threading - if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154 - if (this.isRemoved()) { - return null; -@@ -2397,7 +_,30 @@ - return (Entity)(this.camera == null ? this : this.camera); - } - -+ // Folia start - region threading -+ private void teleportToCameraOffRegion() { -+ Entity cameraFinal = this.camera; -+ // use the task scheduler, as we don't know where the caller is invoking from -+ if (this != cameraFinal) { -+ this.getBukkitEntity().taskScheduler.schedule((final ServerPlayer newPlayer) -> { -+ io.papermc.paper.threadedregions.TeleportUtils.teleport( -+ newPlayer, false, cameraFinal, null, null, 0L, -+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE, null, -+ (final ServerPlayer newerPlayer) -> { -+ return newerPlayer.camera == cameraFinal; -+ } -+ ); -+ }, null, 1L); -+ } // else: do not bother teleporting to self -+ } -+ // Folia end - region threading -+ - public void setCamera(@Nullable Entity entityToSpectate) { -+ // Folia start - region threading -+ if (entityToSpectate != null && (entityToSpectate != this && !entityToSpectate.canBeSpectated())) { -+ return; -+ } -+ // Folia end - region threading - Entity camera = this.getCamera(); - this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate); - if (camera != this.camera) { -@@ -2416,16 +_,19 @@ - } - } - // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity -- if (this.camera.level() instanceof ServerLevel serverLevel) { -- this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit -- } -- -- if (entityToSpectate != null) { -- this.serverLevel().getChunkSource().move(this); -- } -- -+ // Folia - region threading - move down -+ -+ // Folia - region threading - not needed -+ -+ // Folia start - region threading - handle camera setting better -+ if (this.camera == this -+ || (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.camera) && this.camera.moonrise$getTrackedEntity() != null -+ && this.camera.moonrise$getTrackedEntity().seenBy.contains(this.connection))) { -+ // Folia end - region threading - handle camera setting better - this.connection.send(new ClientboundSetCameraPacket(this.camera)); -- this.connection.resetPosition(); -+ } // Folia - region threading - handle camera setting better -+ //this.connection.resetPosition(); // Folia - region threading - not needed -+ this.teleportToCameraOffRegion(); // Folia - region threading - moved down - } - } - -@@ -2896,11 +_,11 @@ - } - - public void registerEnderPearl(ThrownEnderpearl enderPearl) { -- this.enderPearls.add(enderPearl); -+ //this.enderPearls.add(enderPearl); // Folia - region threading - do not track ender pearls - } - - public void deregisterEnderPearl(ThrownEnderpearl enderPearl) { -- this.enderPearls.remove(enderPearl); -+ //this.enderPearls.remove(enderPearl); // Folia - region threading - do not track ender pearls - } - - public Set 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; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch deleted file mode 100644 index 02cda7c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch +++ /dev/null @@ -1,40 +0,0 @@ ---- a/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -114,7 +_,7 @@ - // this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit - this.gameTicks = (int) this.level.getLagCompensationTick(); // Paper - lag compensate eating - if (this.hasDelayedDestroy) { -- BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks -+ BlockState blockState = !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, this.delayedDestroyPos) ? null : this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks // Folia - region threading - don't destroy blocks not owned - if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks - this.hasDelayedDestroy = false; - } else { -@@ -126,7 +_,7 @@ - } - } else if (this.isDestroyingBlock) { - // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead -- BlockState blockState = this.level.getBlockStateIfLoaded(this.destroyPos); -+ BlockState blockState = !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, this.destroyPos) ? null : this.level.getBlockStateIfLoaded(this.destroyPos); // Folia - region threading - don't destroy blocks not owned - if (blockState == null) { - this.isDestroyingBlock = false; - return; -@@ -369,7 +_,7 @@ - } else { - // CraftBukkit start - org.bukkit.block.BlockState state = bblock.getState(); -- this.level.captureDrops = new java.util.ArrayList<>(); -+ this.level.getCurrentWorldData().captureDrops = new java.util.ArrayList<>(); // Folia - region threading - // CraftBukkit end - BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player); - boolean flag = this.level.removeBlock(pos, false); -@@ -395,8 +_,8 @@ - // return true; // CraftBukkit - } - // CraftBukkit start -- java.util.List 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 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 - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/TicketType.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/TicketType.java.patch deleted file mode 100644 index 99262e2..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/TicketType.java.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- a/net/minecraft/server/level/TicketType.java -+++ b/net/minecraft/server/level/TicketType.java -@@ -17,10 +_,18 @@ - public static final TicketType FORCED = create("forced", Comparator.comparingLong(ChunkPos::toLong)); - public static final TicketType PORTAL = create("portal", Vec3i::compareTo, 300); - public static final TicketType ENDER_PEARL = create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40); -- public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); -+ public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 5); // Folia - region threading - public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit - public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit - public static final TicketType POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type -+ // Folia start - region threading -+ public static final TicketType LOGIN = create("folia:login", (u1, u2) -> 0, 20); -+ public static final TicketType DELAYED = create("folia:delay", (u1, u2) -> 0, 5); -+ public static final TicketType END_GATEWAY_EXIT_SEARCH = create("folia:end_gateway_exit_search", Long::compareTo); -+ public static final TicketType NETHER_PORTAL_DOUBLE_CHECK = create("folia:nether_portal_double_check", Long::compareTo); -+ public static final TicketType TELEPORT_HOLD_TICKET = create("folia:teleport_hold_ticket", Long::compareTo); -+ public static final TicketType REGION_SCHEDULER_API_HOLD = create("folia:region_scheduler_api_hold", (a, b) -> 0); -+ // Folia end - region threading - - public static TicketType create(String name, Comparator comparator) { - return new TicketType<>(name, comparator, 0L); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch deleted file mode 100644 index 0f75543..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/net/minecraft/server/level/WorldGenRegion.java -+++ b/net/minecraft/server/level/WorldGenRegion.java -@@ -107,6 +_,13 @@ - return this.getLightEngine().getRawBrightness(blockPos, subtract); - } - // Paper end - rewrite chunk system -+ // Folia start - region threading -+ private final net.minecraft.world.level.StructureManager structureManager; -+ @Override -+ public net.minecraft.world.level.StructureManager structureManager() { -+ return this.structureManager; -+ } -+ // Folia end - region threading - - public WorldGenRegion(ServerLevel level, StaticCache2D 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) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch deleted file mode 100644 index d550f05..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ /dev/null @@ -1,89 +0,0 @@ ---- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -96,6 +_,10 @@ - } - } - -+ // Folia start - region threading -+ private boolean handledDisconnect = false; -+ // Folia end - region threading -+ - @Override - public void onDisconnect(DisconnectionDetails details) { - // Paper start - Fix kick event leave message not being sent -@@ -104,10 +_,18 @@ - - public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { - // Paper end - Fix kick event leave message not being sent -+ // Folia start - region threading -+ if (this.handledDisconnect) { -+ // avoid retiring scheduler twice -+ return; -+ } -+ this.handledDisconnect = true; -+ // Folia end - region threading - if (this.isSingleplayerOwner()) { - LOGGER.info("Stopping singleplayer server as player logged out"); - this.server.halt(false); - } -+ this.player.getBukkitEntity().taskScheduler.retire(); // Folia - region threading - } - - @Override -@@ -330,24 +_,8 @@ - if (this.processedDisconnect) { - return; - } -- if (!this.cserver.isPrimaryThread()) { -- org.bukkit.craftbukkit.util.Waitable waitable = new org.bukkit.craftbukkit.util.Waitable() { -- @Override -- protected Object evaluate() { -- ServerCommonPacketListenerImpl.this.disconnect(disconnectionDetails, cause); // Paper - kick event causes -- return null; -- } -- }; -- -- this.server.processQueue.add(waitable); -- -- try { -- waitable.get(); -- } catch (InterruptedException e) { -- Thread.currentThread().interrupt(); -- } catch (java.util.concurrent.ExecutionException e) { -- throw new RuntimeException(e); -- } -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player)) { // Folia - region threading -+ this.connection.disconnectSafely(disconnectionDetails, cause); // Folia - region threading - it HAS to be delayed/async to avoid deadlock if we try to wait for another region - return; - } - -@@ -378,7 +_,7 @@ - this.onDisconnect(disconnectionDetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message - this.connection.setReadOnly(); - // CraftBukkit - Don't wait -- this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper -+ //this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper // Folia - region threading - } - - // Paper start - add proper async disconnect -@@ -391,19 +_,7 @@ - } - - public void disconnectAsync(DisconnectionDetails disconnectionInfo, org.bukkit.event.player.PlayerKickEvent.Cause cause) { -- if (this.cserver.isPrimaryThread()) { -- this.disconnect(disconnectionInfo, cause); -- return; -- } -- -- this.connection.setReadOnly(); -- this.server.scheduleOnMain(() -> { -- ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); -- if (ServerCommonPacketListenerImpl.this.player.quitReason == null) { -- // cancelled -- ServerCommonPacketListenerImpl.this.connection.enableAutoRead(); -- } -- }); -+ this.disconnect(disconnectionInfo, cause); // Folia - threaded regions - } - // Paper end - add proper async disconnect - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch deleted file mode 100644 index d16a026..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch +++ /dev/null @@ -1,70 +0,0 @@ ---- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -@@ -47,6 +_,7 @@ - private ClientInformation clientInformation; - @Nullable - private SynchronizeRegistriesTask synchronizeRegistriesTask; -+ public boolean switchToMain = false; // Folia - region threading - rewrite login process - - // CraftBukkit start - public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) { -@@ -160,7 +_,58 @@ - } - - ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit -- playerList.placeNewPlayer(this.connection, playerForLogin, this.createCookie(this.clientInformation)); -+ // Folia start - region threading - rewrite login process -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot handle player login off global tick thread"); -+ CommonListenerCookie clientData = this.createCookie(this.clientInformation); -+ org.apache.commons.lang3.mutable.MutableObject data = new org.apache.commons.lang3.mutable.MutableObject<>(); -+ org.apache.commons.lang3.mutable.MutableObject lastKnownName = new org.apache.commons.lang3.mutable.MutableObject<>(); -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable 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 diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch deleted file mode 100644 index 284192d..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch +++ /dev/null @@ -1,28 +0,0 @@ ---- a/net/minecraft/server/network/ServerConnectionListener.java -+++ b/net/minecraft/server/network/ServerConnectionListener.java -@@ -167,12 +_,15 @@ - } - // Paper end - Add support for proxy protocol - // ServerConnectionListener.this.connections.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking -- ServerConnectionListener.this.pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking -+ //ServerConnectionListener.this.pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking // Folia - connection fixes - move down - connection.configurePacketHandler(channelPipeline); - connection.setListenerForServerboundHandshake( - new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection) - ); - io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners -+ // Folia start - regionised threading -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addConnection(connection); -+ // Folia end - regionised threading - } - } - ) -@@ -242,7 +_,7 @@ - // Spigot start - this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking - // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order -- if (org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0) { -+ if (org.spigotmc.SpigotConfig.playerShuffle > 0 && 0 % org.spigotmc.SpigotConfig.playerShuffle == 0) { // Folia - region threading - Collections.shuffle(this.connections); - } - // Spigot end diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch deleted file mode 100644 index c7334d1..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ /dev/null @@ -1,396 +0,0 @@ ---- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -292,10 +_,10 @@ - private int knownMovePacketCount; - private boolean receivedMovementThisTick; - // CraftBukkit start - add fields -- private int lastTick = MinecraftServer.currentTick; -+ private long lastTick = Util.getMillis() / 50L; // Folia - region threading - private int allowedPlayerTicks = 1; -- private int lastDropTick = MinecraftServer.currentTick; -- private int lastBookTick = MinecraftServer.currentTick; -+ private long lastDropTick = Util.getMillis() / 50L; // Folia - region threading -+ private long lastBookTick = Util.getMillis() / 50L; // Folia - region threading - private int dropCount = 0; - - private boolean hasMoved = false; -@@ -313,9 +_,16 @@ - private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20); - private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); - private final FutureChain chatMessageChain; -- private boolean waitingForSwitchToConfig; -+ public volatile boolean waitingForSwitchToConfig; // Folia - rewrite login process - fix bad ordering of this field write + public - private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length - -+ // Folia start - region threading -+ public net.minecraft.world.level.ChunkPos disconnectPos; -+ private static final java.util.concurrent.atomic.AtomicLong DISCONNECT_TICKET_ID_GENERATOR = new java.util.concurrent.atomic.AtomicLong(); -+ public static final net.minecraft.server.level.TicketType DISCONNECT_TICKET = net.minecraft.server.level.TicketType.create("disconnect_ticket", Long::compareTo); -+ public final Long disconnectTicketId = Long.valueOf(DISCONNECT_TICKET_ID_GENERATOR.getAndIncrement()); -+ // Folia end - region threading -+ - public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie cookie) { - super(server, connection, cookie, player); // CraftBukkit - this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection()); -@@ -328,6 +_,12 @@ - - @Override - public void tick() { -+ // Folia start - region threading -+ this.keepConnectionAlive(); -+ if (this.processedDisconnect || this.player.wonGame) { -+ return; -+ } -+ // Folia end - region threading - if (this.ackBlockChangesUpTo > -1) { - this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); - this.ackBlockChangesUpTo = -1; -@@ -376,7 +_,7 @@ - this.aboveGroundVehicleTickCount = 0; - } - -- this.keepConnectionAlive(); -+ // Folia - region threading - moved to beginning of method - this.chatSpamThrottler.tick(); - this.dropSpamThrottler.tick(); - this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits -@@ -412,6 +_,19 @@ - this.lastGoodX = this.player.getX(); - this.lastGoodY = this.player.getY(); - this.lastGoodZ = this.player.getZ(); -+ // Folia start - support vehicle teleportations -+ this.lastVehicle = this.player.getRootVehicle(); -+ if (this.lastVehicle != this.player && this.lastVehicle.getControllingPassenger() == this.player) { -+ this.vehicleFirstGoodX = this.lastVehicle.getX(); -+ this.vehicleFirstGoodY = this.lastVehicle.getY(); -+ this.vehicleFirstGoodZ = this.lastVehicle.getZ(); -+ this.vehicleLastGoodX = this.lastVehicle.getX(); -+ this.vehicleLastGoodY = this.lastVehicle.getY(); -+ this.vehicleLastGoodZ = this.lastVehicle.getZ(); -+ } else { -+ this.lastVehicle = null; -+ } -+ // Folia end - support vehicle teleportations - } - - @Override -@@ -519,9 +_,10 @@ - // Paper end - fix large move vectors killing the server - - // CraftBukkit start - handle custom speeds and skipped ticks -- this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; -+ int currTick = (int)(Util.getMillis() / 50); // Folia - region threading -+ this.allowedPlayerTicks += currTick - this.lastTick; // Folia - region threading - this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); -- this.lastTick = (int) (System.currentTimeMillis() / 50); -+ this.lastTick = (int) currTick; // Folia - region threading - - ++this.receivedMovePacketCount; - int i = this.receivedMovePacketCount - this.knownMovePacketCount; -@@ -588,7 +_,7 @@ - } - - rootVehicle.absMoveTo(d, d1, d2, f, f1); -- this.player.absMoveTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit -+ //this.player.absMoveTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit // Folia - move to repositionAllPassengers - // Paper start - optimise out extra getCubes - boolean teleportBack = flag2; // violating this is always a fail - if (!teleportBack) { -@@ -600,10 +_,18 @@ - } - if (teleportBack) { // Paper end - optimise out extra getCubes - rootVehicle.absMoveTo(x, y, z, f, f1); -- this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit -+ //this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit // Folia - not needed, the player is no longer updated - this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle)); - return; - } -+ -+ // Folia start - move to positionRider -+ // this correction is required on folia since we move the connection tick to the beginning of the server -+ // tick, which would make any desync here visible -+ // this will correctly update the passenger positions for all mounted entities -+ // this prevents desync and ensures that all passengers have the correct rider-adjusted position -+ rootVehicle.repositionAllPassengers(false); -+ // Folia end - move to positionRider - - // CraftBukkit start - fire PlayerMoveEvent - org.bukkit.entity.Player player = this.getCraftPlayer(); -@@ -635,7 +_,7 @@ - - // If the event is cancelled we move the player back to their old location. - if (event.isCancelled()) { -- this.teleport(from); -+ this.player.getBukkitEntity().teleportAsync(from, PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading - return; - } - -@@ -643,7 +_,7 @@ - // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. - // We only do this if the Event was not cancelled. - if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { -- this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); -+ this.player.getBukkitEntity().teleportAsync(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading - return; - } - -@@ -824,7 +_,7 @@ - } - - // This needs to be on main -- this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringReader)); -+ this.player.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> this.sendServerSuggestions(packet, stringReader), null, 1L); // Folia - region threading - } else if (!completions.isEmpty()) { - final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringReader.getTotalLength()); - final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1); -@@ -1207,11 +_,11 @@ - } - // Paper end - Book size limits - // CraftBukkit start -- if (this.lastBookTick + 20 > MinecraftServer.currentTick) { -+ if (this.lastBookTick + 20 > this.lastTick) { // Folia - region threading - this.disconnectAsync(Component.literal("Book edited too quickly!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect - return; - } -- this.lastBookTick = MinecraftServer.currentTick; -+ this.lastBookTick = this.lastTick; // Folia - region threading - // CraftBukkit end - int slot = packet.slot(); - if (Inventory.isHotbarSlot(slot) || slot == 40) { -@@ -1222,7 +_,22 @@ - Consumer> consumer = optional.isPresent() - ? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot) - : texts -> this.updateBookContents(texts, slot); -- this.filterTextPacket(list).thenAcceptAsync(consumer, this.server); -+ // Folia start - region threading -+ this.filterTextPacket(list).thenAcceptAsync( -+ consumer, -+ (Runnable run) -> { -+ this.player.getBukkitEntity().taskScheduler.schedule( -+ (player) -> { -+ run.run(); -+ }, -+ null, 1L); -+ } -+ ).whenComplete((Object res, Throwable thr) -> { -+ if (thr != null) { -+ LOGGER.error("Failed to handle book update packet", thr); -+ } -+ }); -+ // Folia end - region threading - } - } - -@@ -1348,9 +_,10 @@ - int i = this.receivedMovePacketCount - this.knownMovePacketCount; - - // CraftBukkit start - handle custom speeds and skipped ticks -- this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; -+ int currTick = (int)(Util.getMillis() / 50); // Folia - region threading -+ this.allowedPlayerTicks += currTick - this.lastTick; // Folia - region threading - this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); -- this.lastTick = (int) (System.currentTimeMillis() / 50); -+ this.lastTick = (int) currTick; // Folia - region threading - - if (i > Math.max(this.allowedPlayerTicks, 5)) { - LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i); -@@ -1539,7 +_,7 @@ - - // If the event is cancelled we move the player back to their old location. - if (event.isCancelled()) { -- this.teleport(from); -+ this.player.getBukkitEntity().teleportAsync(from, PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading - return; - } - -@@ -1547,7 +_,7 @@ - // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. - // We only do this if the Event was not cancelled. - if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { -- this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); -+ this.player.getBukkitEntity().teleportAsync(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); // Folia - region threading - return; - } - -@@ -1806,9 +_,9 @@ - if (!this.player.isSpectator()) { - // limit how quickly items can be dropped - // If the ticks aren't the same then the count starts from 0 and we update the lastDropTick. -- if (this.lastDropTick != MinecraftServer.currentTick) { -+ if (this.lastDropTick != io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick()) { // Folia - region threading - this.dropCount = 0; -- this.lastDropTick = MinecraftServer.currentTick; -+ this.lastDropTick = io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - region threading - } else { - // Else we increment the drop count and check the amount. - this.dropCount++; -@@ -1836,7 +_,7 @@ - case ABORT_DESTROY_BLOCK: - case STOP_DESTROY_BLOCK: - // Paper start - Don't allow digging into unloaded chunks -- if (this.player.level().getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null) { -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player.serverLevel(), pos.getX() >> 4, pos.getZ() >> 4, 8) || this.player.level().getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null) { // Folia - region threading - don't destroy blocks not owned - this.player.connection.ackBlockChangesUpTo(packet.getSequence()); - return; - } -@@ -1918,7 +_,7 @@ - } - // Paper end - improve distance check - BlockPos blockPos = hitResult.getBlockPos(); -- if (this.player.canInteractWithBlock(blockPos, 1.0)) { -+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.player.serverLevel(), blockPos.getX() >> 4, blockPos.getZ() >> 4, 8) && this.player.canInteractWithBlock(blockPos, 1.0)) { // Folia - do not allow players to interact with blocks outside the current region - Vec3 vec3 = location.subtract(Vec3.atCenterOf(blockPos)); - double d = 1.0000001; - if (Math.abs(vec3.x()) < 1.0000001 && Math.abs(vec3.y()) < 1.0000001 && Math.abs(vec3.z()) < 1.0000001) { -@@ -2039,7 +_,7 @@ - for (ServerLevel serverLevel : this.server.getAllLevels()) { - Entity entity = packet.getEntity(serverLevel); - if (entity != null) { -- this.player.teleportTo(serverLevel, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit -+ io.papermc.paper.threadedregions.TeleportUtils.teleport(this.player, false, entity, null, null, Entity.TELEPORT_FLAG_LOAD_CHUNK, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE, null); // Folia - region threading - return; - } - } -@@ -2071,7 +_,7 @@ - } - // CraftBukkit end - LOGGER.info("{} lost connection: {}", this.player.getName().getString(), details.reason().getString()); -- this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent -+ if (!this.waitingForSwitchToConfig) this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent // Folia - region threading - super.onDisconnect(details, quitMessage); // Paper - Fix kick event leave message not being sent - } - -@@ -2080,6 +_,8 @@ - this.removePlayerFromWorld(null); - } - -+ public boolean hackSwitchingConfig; // Folia - rewrite login process -+ - private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) { - // Paper end - Fix kick event leave message not being sent - this.chatMessageChain.close(); -@@ -2093,6 +_,8 @@ - this.player.disconnect(); - // Paper start - Adventure - quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used -+ if (!this.hackSwitchingConfig) this.disconnectPos = this.player.chunkPosition(); // Folia - region threading - note: only set after removing, since it can tick the player -+ if (!this.hackSwitchingConfig) this.player.serverLevel().chunkSource.addTicketAtLevel(DISCONNECT_TICKET, this.disconnectPos, ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, this.disconnectTicketId); // Folia - region threading - force chunk to be loaded so that the region is not lost - if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { - this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); - // Paper end - Adventure -@@ -2331,7 +_,7 @@ - this.player.resetLastActionTime(); - // CraftBukkit start - if (sync) { -- this.server.execute(handler); -+ this.player.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> handler.run(), null, 1L); // Folia - region threading - } else { - handler.run(); - } -@@ -2386,7 +_,7 @@ - String originalFormat = event.getFormat(), originalMessage = event.getMessage(); - this.cserver.getPluginManager().callEvent(event); - -- if (PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) { -+ if (false && PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) { // Folia - region threading - // Evil plugins still listening to deprecated event - final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients()); - queueEvent.setCancelled(event.isCancelled()); -@@ -2483,6 +_,7 @@ - if (rawMessage.isEmpty()) { - LOGGER.warn("{} tried to send an empty message", this.player.getScoreboardName()); - } else if (this.getCraftPlayer().isConversing()) { -+ if (true) throw new UnsupportedOperationException(); // Folia - region threading - final String conversationInput = rawMessage; - this.server.processQueue.add(() -> ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput)); - } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Re-add "Command Only" flag check -@@ -2708,8 +_,25 @@ - // Spigot end - - public void switchToConfig() { -- this.waitingForSwitchToConfig = true; -+ // Folia start - rewrite login process -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.player, "Cannot switch config off-main"); -+ if (io.papermc.paper.threadedregions.RegionizedServer.isGlobalTickThread()) { -+ throw new IllegalStateException("Cannot switch config while on global tick thread"); -+ } -+ // Folia end - rewrite login process -+ // Folia start - rewrite login process - fix bad ordering of this field write - move after removed from world -+ // the field write ordering is bad as it allows the client to send the response packet before the player is -+ // removed from the world -+ // Folia end - rewrite login process - fix bad ordering of this field write - move after removed from world -+ try { // Folia - rewrite login process - move connection ownership to global region -+ this.hackSwitchingConfig = true; // Folia - rewrite login process - avoid adding logout ticket here and retiring scheduler - this.removePlayerFromWorld(); -+ } finally { // Folia start - rewrite login process - move connection ownership to global region -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.player.serverLevel().getCurrentWorldData(); -+ worldData.connections.remove(this.connection); -+ // once waitingForSwitchToConfig is set, the global tick thread will own the connection -+ } // Folia end - rewrite login process - move connection ownership to global region -+ this.waitingForSwitchToConfig = true; // Folia - rewrite login process - fix bad ordering of this field write - moved down - this.send(ClientboundStartConfigurationPacket.INSTANCE); - this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); - } -@@ -2734,7 +_,7 @@ - // Spigot end - this.player.resetLastActionTime(); - this.player.setShiftKeyDown(packet.isUsingSecondaryAction()); -- if (target != null) { -+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(target) && target != null) { // Folia - region threading - do not allow interaction of entities outside the current region - if (!serverLevel.getWorldBorder().isWithinBounds(target.blockPosition())) { - return; - } -@@ -2866,6 +_,12 @@ - switch (action) { - case PERFORM_RESPAWN: - if (this.player.wonGame) { -+ // Folia start - region threading -+ if (true) { -+ this.player.exitEndCredits(); -+ return; -+ } -+ // Folia end - region threading - this.player.wonGame = false; - this.player = this.server.getPlayerList().respawn(this.player, true, Entity.RemovalReason.CHANGED_DIMENSION, RespawnReason.END_PORTAL); // CraftBukkit - this.resetPosition(); -@@ -2875,6 +_,17 @@ - return; - } - -+ // Folia start - region threading -+ if (true) { -+ this.player.respawn((ServerPlayer player) -> { -+ if (ServerGamePacketListenerImpl.this.server.isHardcore()) { -+ ServerGamePacketListenerImpl.this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent -+ ((GameRules.BooleanValue) ServerGamePacketListenerImpl.this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, ServerGamePacketListenerImpl.this.player.serverLevel()); // CraftBukkit - per-world -+ } -+ }, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH); -+ return; -+ } -+ // Folia end - region threading - this.player = this.server.getPlayerList().respawn(this.player, false, Entity.RemovalReason.KILLED, RespawnReason.DEATH); // CraftBukkit - this.resetPosition(); - if (this.server.isHardcore()) { -@@ -3448,7 +_,21 @@ - } - List 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)list1), this.server); -+ // Folia start - region threading -+ this.filterTextPacket(list).thenAcceptAsync((list1) -> { -+ this.updateSignText(packet, (List)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) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch deleted file mode 100644 index b05b4e1..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch +++ /dev/null @@ -1,33 +0,0 @@ ---- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -111,7 +_,11 @@ - // Paper end - Do not allow logins while the server is shutting down - - if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) { -- if (this.connection.isConnected()) { // Paper - prevent logins to be processed even though disconnect was called -+ // Folia start - region threading - rewrite login process -+ String name = this.authenticatedProfile.getName(); -+ java.util.UUID uniqueId = this.authenticatedProfile.getId(); -+ if (this.server.getPlayerList().pushPendingJoin(name, uniqueId, this.connection)) { -+ // Folia end - region threading - rewrite login process - this.verifyLoginAndFinishConnectionSetup(Objects.requireNonNull(this.authenticatedProfile)); - } // Paper - prevent logins to be processed even though disconnect was called - } -@@ -250,7 +_,7 @@ - ); - } - -- boolean flag = playerList.disconnectAllPlayersWithProfile(profile, this.player); // CraftBukkit - add player reference -+ boolean flag = false && playerList.disconnectAllPlayersWithProfile(profile, this.player); // CraftBukkit - add player reference // Folia - rewrite login process - always false here - if (flag) { - this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT; - } else { -@@ -362,7 +_,7 @@ - uniqueId = gameprofile.getId(); - // Paper end - Add more fields to AsyncPlayerPreLoginEvent - -- if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { -+ if (false && PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { // Folia - region threading - final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId); - if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { - event.disallow(asyncEvent.getResult(), asyncEvent.kickMessage()); // Paper - Adventure diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/players/BanListEntry.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/players/BanListEntry.java.patch deleted file mode 100644 index d7018ee..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/players/BanListEntry.java.patch +++ /dev/null @@ -1,50 +0,0 @@ ---- a/net/minecraft/server/players/BanListEntry.java -+++ b/net/minecraft/server/players/BanListEntry.java -@@ -9,7 +_,7 @@ - import net.minecraft.network.chat.Component; - - public abstract class BanListEntry extends StoredUserEntry { -- public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT); -+ public static final ThreadLocal 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 - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch deleted file mode 100644 index cb2ee52..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/server/players/OldUsersConverter.java -+++ b/net/minecraft/server/players/OldUsersConverter.java -@@ -469,7 +_,7 @@ - static Date parseDate(String input, Date defaultValue) { - Date date; - try { -- date = BanListEntry.DATE_FORMAT.parse(input); -+ date = BanListEntry.DATE_FORMAT.get().parse(input); // Folia - region threading - SDF is not thread-safe - } catch (ParseException var4) { - date = defaultValue; - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch deleted file mode 100644 index e9c0812..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch +++ /dev/null @@ -1,419 +0,0 @@ ---- a/net/minecraft/server/players/PlayerList.java -+++ b/net/minecraft/server/players/PlayerList.java -@@ -110,10 +_,10 @@ - public static final Component DUPLICATE_LOGIN_DISCONNECT_MESSAGE = Component.translatable("multiplayer.disconnect.duplicate_login"); - private static final Logger LOGGER = LogUtils.getLogger(); - private static final int SEND_PLAYER_INFO_INTERVAL = 600; -- private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); -+ private static final ThreadLocal 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 players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety -- private final Map playersByUUID = Maps.newHashMap(); -+ private final Map 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 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 connectionByName = new java.util.HashMap<>(); -+ private final Map connectionById = new java.util.HashMap<>(); -+ private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet 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 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 data, org.apache.commons.lang3.mutable.MutableObject lastKnownName, ca.spottedleaf.concurrentutil.completable.CallbackCompletable 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 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 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 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 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); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/server/players/StoredUserList.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/server/players/StoredUserList.java.patch deleted file mode 100644 index 8565367..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/server/players/StoredUserList.java.patch +++ /dev/null @@ -1,29 +0,0 @@ ---- a/net/minecraft/server/players/StoredUserList.java -+++ b/net/minecraft/server/players/StoredUserList.java -@@ -97,6 +_,7 @@ - } - - public void save() throws IOException { -+ synchronized (this) { // Folia - region threading - this.removeExpired(); // Paper - remove expired values before saving - JsonArray jsonArray = new JsonArray(); - this.map.values().stream().map(storedEntry -> Util.make(new JsonObject(), storedEntry::serialize)).forEach(jsonArray::add); -@@ -104,9 +_,11 @@ - try (BufferedWriter writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) { - GSON.toJson(jsonArray, GSON.newJsonWriter(writer)); - } -+ } // Folia - region threading - } - - public void load() throws IOException { -+ synchronized (this) { // Folia - region threading - if (this.file.exists()) { - try (BufferedReader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) { - this.map.clear(); -@@ -131,5 +_,6 @@ - } - // Spigot end - } -+ } // Folia - region threading - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/util/SpawnUtil.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/util/SpawnUtil.java.patch deleted file mode 100644 index 7d597d0..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/util/SpawnUtil.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/util/SpawnUtil.java -+++ b/net/minecraft/util/SpawnUtil.java -@@ -83,7 +_,7 @@ - return Optional.of(mob); - } - -- mob.discard(null); // CraftBukkit - add Bukkit remove cause -+ //mob.discard(null); // CraftBukkit - add Bukkit remove cause // Folia - region threading - } - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/RandomSequences.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/RandomSequences.java.patch deleted file mode 100644 index 0695ffb..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/RandomSequences.java.patch +++ /dev/null @@ -1,83 +0,0 @@ ---- a/net/minecraft/world/RandomSequences.java -+++ b/net/minecraft/world/RandomSequences.java -@@ -21,7 +_,7 @@ - private int salt; - private boolean includeWorldSeed = true; - private boolean includeSequenceId = true; -- private final Map sequences = new Object2ObjectOpenHashMap<>(); -+ private final Map sequences = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - region threading - - public static SavedData.Factory 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 diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/CombatTracker.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/CombatTracker.java.patch deleted file mode 100644 index 4435c97..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/CombatTracker.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/net/minecraft/world/damagesource/CombatTracker.java -+++ b/net/minecraft/world/damagesource/CombatTracker.java -@@ -53,7 +_,7 @@ - } - - private Component getMessageForAssistedFall(Entity entity, Component entityDisplayName, String hasWeaponTranslationKey, String noWeaponTranslationKey) { -- ItemStack itemStack = entity instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY; -+ ItemStack itemStack = entity instanceof LivingEntity livingEntity && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(livingEntity) ? livingEntity.getMainHandItem() : ItemStack.EMPTY; // Folia - region threading - return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME) - ? Component.translatable(hasWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName, itemStack.getDisplayName()) - : Component.translatable(noWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName); -@@ -80,7 +_,7 @@ - - @Nullable - private static Component getDisplayName(@Nullable Entity entity) { -- return entity == null ? null : entity.getDisplayName(); -+ return entity == null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity) ? null : entity.getDisplayName(); // Folia - region threading - } - - public Component getDeathMessage() { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch deleted file mode 100644 index ca0d9b8..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/net/minecraft/world/damagesource/DamageSource.java -+++ b/net/minecraft/world/damagesource/DamageSource.java -@@ -163,12 +_,12 @@ - if (this.causingEntity == null && this.directEntity == null) { - LivingEntity killCredit = livingEntity.getKillCredit(); - String string1 = string + ".player"; -- return killCredit != null -+ return killCredit != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(killCredit) - ? Component.translatable(string1, livingEntity.getDisplayName(), killCredit.getDisplayName()) - : Component.translatable(string, livingEntity.getDisplayName()); - } else { - Component component = this.causingEntity == null ? this.directEntity.getDisplayName() : this.causingEntity.getDisplayName(); -- ItemStack itemStack = this.causingEntity instanceof LivingEntity livingEntity1 ? livingEntity1.getMainHandItem() : ItemStack.EMPTY; -+ ItemStack itemStack = this.causingEntity instanceof LivingEntity livingEntity1 && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(livingEntity1) ? livingEntity1.getMainHandItem() : ItemStack.EMPTY; // Folia - region threading - return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME) - ? Component.translatable(string + ".item", livingEntity.getDisplayName(), component, itemStack.getDisplayName()) - : Component.translatable(string, livingEntity.getDisplayName(), component); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/FallLocation.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/FallLocation.java.patch deleted file mode 100644 index 42cd8c4..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/damagesource/FallLocation.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/damagesource/FallLocation.java -+++ b/net/minecraft/world/damagesource/FallLocation.java -@@ -35,7 +_,7 @@ - @Nullable - public static FallLocation getCurrentFallLocation(LivingEntity entity) { - Optional 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 { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch deleted file mode 100644 index bbb95ec..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch +++ /dev/null @@ -1,1088 +0,0 @@ ---- a/net/minecraft/world/entity/Entity.java -+++ b/net/minecraft/world/entity/Entity.java -@@ -145,7 +_,7 @@ - } - - // Paper start - Share random for entities to make them more random -- public static RandomSource SHARED_RANDOM = new RandomRandomSource(); -+ public static RandomSource SHARED_RANDOM = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Folia - region threading - // Paper start - replace random - private static final class RandomRandomSource extends ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom { - public RandomRandomSource() { -@@ -175,7 +_,7 @@ - public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason - - public boolean collisionLoadChunks = false; // Paper -- private @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; -+ private volatile @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; // Folia - region threading - - public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() { - if (this.bukkitEntity == null) { -@@ -294,7 +_,7 @@ - private boolean hasGlowingTag; - private final Set tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl - private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0}; -- private long pistonDeltasGameTime; -+ private long pistonDeltasGameTime = Long.MIN_VALUE; // Folia - region threading - private EntityDimensions dimensions; - private float eyeHeight; - public boolean isInPowderSnow; -@@ -521,6 +_,23 @@ - } - } - // Paper end - optimise entity tracker -+ // Folia start - region ticking -+ public void updateTicks(long fromTickOffset, long fromRedstoneTimeOffset) { -+ if (this.activatedTick != Integer.MIN_VALUE) { -+ this.activatedTick += fromTickOffset; -+ } -+ if (this.activatedImmunityTick != Integer.MIN_VALUE) { -+ this.activatedImmunityTick += fromTickOffset; -+ } -+ if (this.pistonDeltasGameTime != Long.MIN_VALUE) { -+ this.pistonDeltasGameTime += fromRedstoneTimeOffset; -+ } -+ } -+ -+ public boolean canBeSpectated() { -+ return !this.getBukkitEntity().taskScheduler.isRetired(); -+ } -+ // Folia end - region ticking - - public Entity(EntityType entityType, Level level) { - this.type = entityType; -@@ -651,8 +_,7 @@ - // due to interactions on the client. - public void resendPossiblyDesyncedEntityData(net.minecraft.server.level.ServerPlayer player) { - if (player.getBukkitEntity().canSee(this.getBukkitEntity())) { -- ServerLevel world = (net.minecraft.server.level.ServerLevel)this.level(); -- net.minecraft.server.level.ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.getId()); -+ net.minecraft.server.level.ChunkMap.TrackedEntity tracker = this.moonrise$getTrackedEntity(); // Folia - region threading - if (tracker == null) { - return; - } -@@ -819,7 +_,7 @@ - public void postTick() { - // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle - if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities -- this.handlePortal(); -+ //this.handlePortal(); // Folia - region threading - } - } - // CraftBukkit end -@@ -837,7 +_,7 @@ - this.boardingCooldown--; - } - -- if (this instanceof ServerPlayer) this.handlePortal(); // CraftBukkit - // Moved up to postTick -+ //if (this instanceof ServerPlayer) this.handlePortal(); // CraftBukkit - // Moved up to postTick // Folia - region threading - ONLY allow in postTick() - if (this.canSpawnSprintParticle()) { - this.spawnSprintParticle(); - } -@@ -1100,8 +_,8 @@ - } else { - this.wasOnFire = this.isOnFire(); - if (type == MoverType.PISTON) { -- this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20); // Paper - EAR 2 -- this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20); // Paper - EAR 2 -+ this.activatedTick = Math.max(this.activatedTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 20); // Paper - EAR 2 // Folia - region threading -+ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 20); // Paper - EAR 2 // Folia - region threading - movement = this.limitPistonMovement(movement); - if (movement.equals(Vec3.ZERO)) { - return; -@@ -1400,7 +_,7 @@ - if (pos.lengthSqr() <= 1.0E-7) { - return pos; - } else { -- long gameTime = this.level().getGameTime(); -+ long gameTime = this.level().getRedstoneGameTime(); // Folia - region threading - if (gameTime != this.pistonDeltasGameTime) { - Arrays.fill(this.pistonDeltas, 0.0); - this.pistonDeltasGameTime = gameTime; -@@ -3034,6 +_,7 @@ - } - - 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()); -@@ -3055,6 +_,7 @@ - return false; - } - // CraftBukkit end -+ } // Folia - region threading - suppress entire event logic during worldgen - if (this.isPassenger()) { - this.stopRiding(); - } -@@ -3122,7 +_,7 @@ - this.passengers = ImmutableList.copyOf(list); - } - -- this.gameEvent(GameEvent.ENTITY_MOUNT, passenger); -+ if (!passenger.hasNullCallback()) this.gameEvent(GameEvent.ENTITY_MOUNT, passenger); // Folia - region threading - do not fire game events for entities not added - } - } - -@@ -3136,6 +_,7 @@ - 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) { -@@ -3163,6 +_,7 @@ - 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 { -@@ -3170,7 +_,7 @@ - } - - passenger.boardingCooldown = 60; -- this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger); -+ if (!passenger.hasNullCallback()) this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger); // Folia - region threading - do not fire game events for entities not added - } - return true; // CraftBukkit - } -@@ -3254,7 +_,7 @@ - } - } - -- protected void handlePortal() { -+ public boolean handlePortal() { // Folia - region threading - public, ret type -> boolean - if (this.level() instanceof ServerLevel serverLevel) { - this.processPortalCooldown(); - if (this.portalProcess != null) { -@@ -3262,21 +_,20 @@ - ProfilerFiller profilerFiller = Profiler.get(); - profilerFiller.push("portal"); - this.setPortalCooldown(); -- TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this); -- if (portalDestination != null) { -- ServerLevel level = portalDestination.newLevel(); -- if (this instanceof ServerPlayer // CraftBukkit - always call event for players -- || (level != null && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)))) { // CraftBukkit -- this.teleport(portalDestination); -- } -+ // Folia start - region threading -+ try { -+ return this.portalProcess.portalAsync(serverLevel, this); -+ } finally { -+ profilerFiller.pop(); - } -- -- profilerFiller.pop(); -+ // Folia end - region threading - } else if (this.portalProcess.hasExpired()) { - this.portalProcess = null; - } - } - } -+ -+ return false; // Folia - region threading - } - - public int getDimensionChangingDelay() { -@@ -3416,6 +_,11 @@ - - @Nullable - public PlayerTeam getTeam() { -+ // Folia start - region threading -+ if (true) { -+ return null; -+ } -+ // Folia end - region threading - if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper - Perf: Disable Scoreboards for non players by default - return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName()); - } -@@ -3722,8 +_,793 @@ - this.portalProcess = entity.portalProcess; - } - -+ // Folia start - region threading -+ public static class EntityTreeNode { -+ @Nullable -+ public EntityTreeNode parent; -+ public Entity root; -+ @Nullable -+ public EntityTreeNode[] passengers; -+ -+ public EntityTreeNode(EntityTreeNode parent, Entity root) { -+ this.parent = parent; -+ this.root = root; -+ } -+ -+ public EntityTreeNode(EntityTreeNode parent, Entity root, EntityTreeNode[] passengers) { -+ this.parent = parent; -+ this.root = root; -+ this.passengers = passengers; -+ } -+ -+ public List getFullTree() { -+ List ret = new java.util.ArrayList<>(); -+ ret.add(this); -+ -+ // this is just a BFS except we don't remove from head, we just advance down the list -+ for (int i = 0; i < ret.size(); ++i) { -+ EntityTreeNode node = ret.get(i); -+ -+ EntityTreeNode[] passengers = node.passengers; -+ if (passengers == null) { -+ continue; -+ } -+ for (EntityTreeNode passenger : passengers) { -+ ret.add(passenger); -+ } -+ } -+ -+ return ret; -+ } -+ -+ public void restore() { -+ java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); -+ queue.add(this); -+ -+ EntityTreeNode curr; -+ while ((curr = queue.pollFirst()) != null) { -+ EntityTreeNode[] passengers = curr.passengers; -+ if (passengers == null) { -+ continue; -+ } -+ -+ List newPassengers = new java.util.ArrayList<>(); -+ for (EntityTreeNode passenger : passengers) { -+ newPassengers.add(passenger.root); -+ passenger.root.vehicle = curr.root; -+ } -+ -+ curr.root.passengers = ImmutableList.copyOf(newPassengers); -+ } -+ } -+ -+ public void addTracker() { -+ for (final EntityTreeNode node : this.getFullTree()) { -+ if (node.root.moonrise$getTrackedEntity() != null) { -+ for (final ServerPlayer player : node.root.level.getLocalPlayers()) { -+ node.root.moonrise$getTrackedEntity().updatePlayer(player); -+ } -+ } -+ } -+ } -+ -+ public void clearTracker() { -+ for (final EntityTreeNode node : this.getFullTree()) { -+ if (node.root.moonrise$getTrackedEntity() != null) { -+ node.root.moonrise$getTrackedEntity().moonrise$removeNonTickThreadPlayers(); -+ for (final ServerPlayer player : node.root.level.getLocalPlayers()) { -+ node.root.moonrise$getTrackedEntity().removePlayer(player); -+ } -+ } -+ } -+ } -+ -+ public void adjustRiders(boolean teleport) { -+ java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); -+ queue.add(this); -+ -+ EntityTreeNode curr; -+ while ((curr = queue.pollFirst()) != null) { -+ EntityTreeNode[] passengers = curr.passengers; -+ if (passengers == null) { -+ continue; -+ } -+ -+ for (EntityTreeNode passenger : passengers) { -+ curr.root.positionRider(passenger.root, teleport ? Entity::moveTo : Entity::setPos); -+ } -+ } -+ } -+ } -+ -+ public void repositionAllPassengers(boolean teleport) { -+ this.makePassengerTree().adjustRiders(teleport); -+ } -+ -+ protected EntityTreeNode makePassengerTree() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot read passengers off of the main thread"); -+ -+ EntityTreeNode root = new EntityTreeNode(null, this); -+ java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); -+ queue.add(root); -+ EntityTreeNode curr; -+ while ((curr = queue.pollFirst()) != null) { -+ Entity vehicle = curr.root; -+ List passengers = vehicle.passengers; -+ if (passengers.isEmpty()) { -+ continue; -+ } -+ -+ EntityTreeNode[] treePassengers = new EntityTreeNode[passengers.size()]; -+ curr.passengers = treePassengers; -+ -+ for (int i = 0; i < passengers.size(); ++i) { -+ Entity passenger = passengers.get(i); -+ queue.addLast(treePassengers[i] = new EntityTreeNode(curr, passenger)); -+ } -+ } -+ -+ return root; -+ } -+ -+ protected EntityTreeNode detachPassengers() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot adjust passengers/vehicle off of the main thread"); -+ -+ EntityTreeNode root = new EntityTreeNode(null, this); -+ java.util.ArrayDeque queue = new java.util.ArrayDeque<>(); -+ queue.add(root); -+ EntityTreeNode curr; -+ while ((curr = queue.pollFirst()) != null) { -+ Entity vehicle = curr.root; -+ List passengers = vehicle.passengers; -+ if (passengers.isEmpty()) { -+ continue; -+ } -+ -+ vehicle.passengers = ImmutableList.of(); -+ -+ EntityTreeNode[] treePassengers = new EntityTreeNode[passengers.size()]; -+ curr.passengers = treePassengers; -+ -+ for (int i = 0; i < passengers.size(); ++i) { -+ Entity passenger = passengers.get(i); -+ passenger.vehicle = null; -+ queue.addLast(treePassengers[i] = new EntityTreeNode(curr, passenger)); -+ } -+ } -+ -+ return root; -+ } -+ -+ /** -+ * This flag will perform an async load on the chunks determined by -+ * the entity's bounding box before teleporting the entity. -+ */ -+ public static final long TELEPORT_FLAG_LOAD_CHUNK = 1L << 0; -+ /** -+ * This flag requires the entity being teleported to be a root vehicle. -+ * Thus, if you want to teleport a non-root vehicle, you must dismount -+ * the target entity before calling teleport, otherwise the -+ * teleport will be refused. -+ */ -+ public static final long TELEPORT_FLAG_TELEPORT_PASSENGERS = 1L << 1; -+ /** -+ * The flag will dismount any passengers and dismout from the current vehicle -+ * to teleport if and only if dismounting would result in the teleport being allowed. -+ */ -+ public static final long TELEPORT_FLAG_UNMOUNT = 1L << 2; -+ -+ protected void placeSingleSync(ServerLevel originWorld, ServerLevel destination, EntityTreeNode treeNode, long teleportFlags) { -+ destination.addDuringTeleport(this); -+ } -+ -+ protected final void placeInAsync(ServerLevel originWorld, ServerLevel destination, long teleportFlags, -+ EntityTreeNode passengerTree, java.util.function.Consumer teleportComplete) { -+ Vec3 pos = this.position(); -+ ChunkPos posChunk = new ChunkPos( -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos) -+ ); -+ -+ // ensure the region is always ticking in case of a shutdown -+ // otherwise, the shutdown will not be able to complete the shutdown as it requires a ticking region -+ Long teleportHoldId = Long.valueOf(TELEPORT_HOLD_TICKET_GEN.getAndIncrement()); -+ originWorld.chunkSource.addTicketAtLevel( -+ TicketType.TELEPORT_HOLD_TICKET, posChunk, -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, -+ teleportHoldId -+ ); -+ final ServerLevel.PendingTeleport pendingTeleport = new ServerLevel.PendingTeleport(passengerTree, pos); -+ destination.pushPendingTeleport(pendingTeleport); -+ -+ Runnable scheduleEntityJoin = () -> { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( -+ destination, -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos), -+ () -> { -+ if (!destination.removePendingTeleport(pendingTeleport)) { -+ // shutdown logic placed the entity already, and we are shutting down - do nothing to ensure -+ // we do not produce any errors here -+ return; -+ } -+ originWorld.chunkSource.removeTicketAtLevel( -+ TicketType.TELEPORT_HOLD_TICKET, posChunk, -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, -+ teleportHoldId -+ ); -+ List fullTree = passengerTree.getFullTree(); -+ for (EntityTreeNode node : fullTree) { -+ node.root.placeSingleSync(originWorld, destination, node, teleportFlags); -+ } -+ -+ // restore passenger tree -+ passengerTree.restore(); -+ passengerTree.adjustRiders(true); -+ -+ // invoke post dimension change now -+ for (EntityTreeNode node : fullTree) { -+ node.root.postChangeDimension(); -+ } -+ -+ if (teleportComplete != null) { -+ teleportComplete.accept(Entity.this); -+ } -+ } -+ ); -+ }; -+ -+ if ((teleportFlags & TELEPORT_FLAG_LOAD_CHUNK) != 0L) { -+ destination.loadChunksForMoveAsync( -+ this.getBoundingBox(), ca.spottedleaf.concurrentutil.util.Priority.HIGHER, -+ (chunkList) -> { -+ for (net.minecraft.world.level.chunk.ChunkAccess chunk : chunkList) { -+ destination.chunkSource.addTicketAtLevel( -+ TicketType.POST_TELEPORT, chunk.getPos(), -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.FULL_LOADED_TICKET_LEVEL, -+ Integer.valueOf(Entity.this.getId()) -+ ); -+ } -+ scheduleEntityJoin.run(); -+ } -+ ); -+ } else { -+ scheduleEntityJoin.run(); -+ } -+ } -+ -+ protected boolean canTeleportAsync() { -+ return !this.hasNullCallback() && !this.isRemoved() && this.isAlive() && (!(this instanceof net.minecraft.world.entity.LivingEntity livingEntity) || !livingEntity.isSleeping()); -+ } -+ -+ // Mojang for whatever reason has started storing positions to cache certain physics properties that entities collide with -+ // As usual though, they don't properly do anything to prevent serious desync with respect to the current entity position -+ // We add additional logic to reset these before teleporting to prevent issues with them possibly tripping thread checks. -+ protected void resetStoredPositions() { -+ this.mainSupportingBlockPos = Optional.empty(); -+ // this is copied from teleportSetPosition -+ this.movementThisTick.clear(); -+ } -+ -+ 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.moveTo(pos.x, pos.y, pos.z); -+ this.setOldPosAndRot(); -+ this.resetStoredPositions(); -+ } -+ -+ protected final void transform(TeleportTransition telpeort) { -+ PositionMoveRotation move = PositionMoveRotation.calculateAbsolute( -+ PositionMoveRotation.of(this), PositionMoveRotation.of(telpeort), telpeort.relatives() -+ ); -+ this.transform( -+ move.position(), Float.valueOf(move.yRot()), Float.valueOf(move.xRot()), move.deltaMovement() -+ ); -+ } -+ -+ protected void transform(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); -+ } -+ if (pos != null) { -+ this.setPosRaw(pos.x, pos.y, pos.z); -+ } -+ this.setOldPosAndRot(); -+ } -+ -+ protected final Entity transformForAsyncTeleport(TeleportTransition telpeort) { -+ PositionMoveRotation move = PositionMoveRotation.calculateAbsolute( -+ PositionMoveRotation.of(this), PositionMoveRotation.of(telpeort), telpeort.relatives() -+ ); -+ return this.transformForAsyncTeleport( -+ telpeort.newLevel(), telpeort.position(), Float.valueOf(move.yRot()), Float.valueOf(move.xRot()), move.deltaMovement() -+ ); -+ } -+ -+ protected Entity transformForAsyncTeleport(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity) { -+ this.removeAfterChangingDimensions(); // remove before so that any CBEntity#getHandle call affects this entity before copying -+ -+ Entity copy = this.getType().create(destination, EntitySpawnReason.DIMENSION_TRAVEL); -+ copy.restoreFrom(this); -+ copy.transform(pos, yaw, pitch, velocity); -+ // vanilla code used to call remove _after_ copying, and some stuff is required to be after copy - so add hook here -+ // for example, clearing of inventory after switching dimensions -+ this.postRemoveAfterChangingDimensions(); -+ -+ return copy; -+ } -+ -+ public final boolean teleportAsync(TeleportTransition teleportTarget, long teleportFlags, -+ java.util.function.Consumer teleportComplete) { -+ PositionMoveRotation move = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); -+ -+ return this.teleportAsync( -+ teleportTarget.newLevel(), move.position(), Float.valueOf(move.yRot()), Float.valueOf(move.xRot()), move.deltaMovement(), -+ teleportTarget.cause(), teleportFlags, teleportComplete -+ ); -+ } -+ -+ public final boolean teleportAsync(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity, -+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, long teleportFlags, -+ java.util.function.Consumer teleportComplete) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot teleport entity async"); -+ -+ if (!ServerLevel.isInSpawnableBounds(new BlockPos(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockY(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getBlockZ(pos)))) { -+ return false; -+ } -+ -+ if (!this.canTeleportAsync()) { -+ return false; -+ } -+ this.getBukkitEntity(); // force bukkit entity to be created before TPing -+ if ((teleportFlags & TELEPORT_FLAG_UNMOUNT) == 0L) { -+ for (Entity entity : this.getIndirectPassengers()) { -+ if (!entity.canTeleportAsync()) { -+ return false; -+ } -+ entity.getBukkitEntity(); // force bukkit entity to be created before TPing -+ } -+ } else { -+ this.unRide(); -+ } -+ -+ if ((teleportFlags & TELEPORT_FLAG_TELEPORT_PASSENGERS) != 0L) { -+ if (this.isPassenger()) { -+ return false; -+ } -+ } else { -+ if (this.isVehicle() || this.isPassenger()) { -+ return false; -+ } -+ } -+ -+ // TODO any events that can modify go HERE -+ -+ // check for same region -+ if (destination == this.level()) { -+ Vec3 currPos = this.position(); -+ if ( -+ destination.regioniser.getRegionAtUnsynchronised( -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(currPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(currPos) -+ ) == destination.regioniser.getRegionAtUnsynchronised( -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(pos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(pos) -+ ) -+ ) { -+ boolean hasPassengers = !this.passengers.isEmpty(); -+ EntityTreeNode passengerTree = this.detachPassengers(); -+ -+ if (hasPassengers) { -+ // Note: The client does not accept position updates for controlled entities. So, we must -+ // perform a lot of tracker updates here to make it all work out. -+ -+ // first, clear the tracker -+ passengerTree.clearTracker(); -+ } -+ -+ for (EntityTreeNode entity : passengerTree.getFullTree()) { -+ entity.root.teleportSyncSameRegion(pos, yaw, pitch, velocity); -+ } -+ -+ if (hasPassengers) { -+ passengerTree.restore(); -+ // re-add to the tracker once the tree is restored -+ passengerTree.addTracker(); -+ -+ // adjust entities to final position -+ passengerTree.adjustRiders(true); -+ -+ // the tracker clear/add logic is only used in the same region, as the other logic -+ // performs add/remove from world logic which will also perform add/remove tracker logic -+ } -+ -+ if (teleportComplete != null) { -+ teleportComplete.accept(this); -+ } -+ return true; -+ } -+ } -+ -+ EntityTreeNode passengerTree = this.detachPassengers(); -+ List fullPassengerTree = passengerTree.getFullTree(); -+ ServerLevel originWorld = (ServerLevel)this.level; -+ -+ for (EntityTreeNode node : fullPassengerTree) { -+ node.root.preChangeDimension(); -+ } -+ -+ for (EntityTreeNode node : fullPassengerTree) { -+ node.root = node.root.transformForAsyncTeleport(destination, pos, yaw, pitch, velocity); -+ } -+ -+ passengerTree.root.placeInAsync(originWorld, destination, teleportFlags, passengerTree, teleportComplete); -+ -+ return true; -+ } -+ -+ public void preChangeDimension() { -+ if (this instanceof Leashable leashable) { -+ leashable.dropLeash(); -+ } -+ } -+ -+ public void postChangeDimension() { -+ this.resetStoredPositions(); -+ } -+ -+ protected static enum PortalType { -+ NETHER, END; -+ } -+ -+ public boolean endPortalLogicAsync(BlockPos portalPos) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async"); -+ -+ ServerLevel destination = this.getServer().getLevel(this.level().getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END ? Level.OVERWORLD : Level.END); -+ if (destination == null) { -+ // wat -+ return false; -+ } -+ -+ return this.portalToAsync(destination, portalPos, true, PortalType.END, null); -+ } -+ -+ public boolean netherPortalLogicAsync(BlockPos portalPos) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async"); -+ -+ ServerLevel destination = this.getServer().getLevel(this.level().getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER); -+ if (destination == null) { -+ // wat -+ return false; -+ } -+ -+ return this.portalToAsync(destination, portalPos, true, PortalType.NETHER, null); -+ } -+ -+ private static final java.util.concurrent.atomic.AtomicLong CREATE_PORTAL_DOUBLE_CHECK = new java.util.concurrent.atomic.AtomicLong(); -+ private static final java.util.concurrent.atomic.AtomicLong TELEPORT_HOLD_TICKET_GEN = new java.util.concurrent.atomic.AtomicLong(); -+ -+ // To simplify portal logic, in region threading both players -+ // and non-player entities will create portals. By guaranteeing -+ // that the teleportation can take place, we can simply -+ // remove the entity, find/create the portal, and place async. -+ // If we have to worry about whether the entity may not teleport, -+ // we need to first search, then report back, ... -+ protected void findOrCreatePortalAsync(ServerLevel origin, BlockPos originPortal, ServerLevel destination, PortalType type, -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable portalInfoCompletable) { -+ switch (type) { -+ // end portal logic is quite simple, the spawn in the end is fixed and when returning to the overworld -+ // we just select the spawn position -+ case END: { -+ if (destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) { -+ BlockPos targetPos = ServerLevel.END_SPAWN_POINT; -+ // need to load chunks so we can create the platform -+ destination.moonrise$loadChunksAsync( -+ targetPos, 16, // load 16 blocks to be safe from block physics -+ ca.spottedleaf.concurrentutil.util.Priority.HIGH, -+ (chunks) -> { -+ net.minecraft.world.level.levelgen.feature.EndPlatformFeature.createEndPlatform(destination, targetPos.below(), true, null); -+ -+ // the portal obsidian is placed at targetPos.y - 2, so if we want to place the entity -+ // on the obsidian, we need to spawn at targetPos.y - 1 -+ portalInfoCompletable.complete( -+ new net.minecraft.world.level.portal.TeleportTransition( -+ destination, Vec3.atBottomCenterOf(targetPos.below()), Vec3.ZERO, Direction.WEST.toYRot(), 0.0f, -+ Relative.union(Relative.DELTA, Set.of(Relative.X_ROT)), -+ TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET), -+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL -+ ) -+ ); -+ } -+ ); -+ } else { -+ BlockPos spawnPos = destination.getSharedSpawnPos(); -+ // need to load chunk for heightmap -+ destination.moonrise$loadChunksAsync( -+ spawnPos, 0, -+ ca.spottedleaf.concurrentutil.util.Priority.HIGH, -+ (chunks) -> { -+ BlockPos adjustedSpawn = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, spawnPos); -+ -+ // done -+ portalInfoCompletable.complete( -+ new net.minecraft.world.level.portal.TeleportTransition( -+ destination, Vec3.atBottomCenterOf(adjustedSpawn), Vec3.ZERO, 0.0f, 0.0f, -+ Relative.union(Relative.DELTA, Relative.ROTATION), -+ TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET), -+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL -+ ) -+ ); -+ } -+ ); -+ } -+ -+ break; -+ } -+ // for the nether logic, we need to first load the chunks in radius to empty (so that POI is created) -+ // then we can search for an existing portal using the POI routines -+ // if we don't find a portal, then we bring the chunks in the create radius to full and -+ // create it -+ case NETHER: { -+ // hoisted from the create fallback, so that we can avoid the sync load later if we need it -+ BlockState originalPortalBlock = origin.getBlockStateIfLoaded(originPortal); -+ Direction.Axis originalPortalDirection = originalPortalBlock == null ? Direction.Axis.X : -+ originalPortalBlock.getOptionalValue(net.minecraft.world.level.block.NetherPortalBlock.AXIS).orElse(Direction.Axis.X); -+ BlockUtil.FoundRectangle originalPortalRectangle = -+ originalPortalBlock == null || !originalPortalBlock.hasProperty(net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_AXIS) -+ ? null -+ : BlockUtil.getLargestRectangleAround( -+ originPortal, originalPortalDirection, 21, Direction.Axis.Y, 21, -+ (blockpos) -> { -+ return origin.getBlockStateFromEmptyChunkIfLoaded(blockpos) == originalPortalBlock; -+ } -+ ); -+ -+ boolean destinationIsNether = destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER; -+ -+ int portalSearchRadius = origin.paperConfig().environment.portalSearchVanillaDimensionScaling && destinationIsNether ? -+ (int)(destination.paperConfig().environment.portalSearchRadius / destination.dimensionType().coordinateScale()) : -+ destination.paperConfig().environment.portalSearchRadius; -+ int portalCreateRadius = destination.paperConfig().environment.portalCreateRadius; -+ -+ WorldBorder destinationBorder = destination.getWorldBorder(); -+ double dimensionScale = net.minecraft.world.level.dimension.DimensionType.getTeleportationScale(origin.dimensionType(), destination.dimensionType()); -+ BlockPos targetPos = destination.getWorldBorder().clampToBounds(this.getX() * dimensionScale, this.getY(), this.getZ() * dimensionScale); -+ -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable portalFound -+ = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); -+ -+ // post portal find/create logic -+ portalFound.addWaiter( -+ (BlockUtil.FoundRectangle portal, Throwable thr) -> { -+ // no portal could be created -+ if (portal == null) { -+ portalInfoCompletable.complete( -+ new TeleportTransition(destination, Vec3.atCenterOf(targetPos), Vec3.ZERO, -+ 90.0f, 0.0f, -+ TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET)) -+ ); -+ return; -+ } -+ -+ Vec3 relativePos = originalPortalRectangle == null ? -+ new Vec3(0.5, 0.0, 0.0) : -+ Entity.this.getRelativePortalPosition(originalPortalDirection, originalPortalRectangle); -+ -+ portalInfoCompletable.complete( -+ net.minecraft.world.level.block.NetherPortalBlock.createDimensionTransition( -+ destination, portal, originalPortalDirection, relativePos, -+ Entity.this, TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET) -+ ) -+ ); -+ } -+ ); -+ -+ // kick off search for existing portal or creation -+ destination.moonrise$loadChunksAsync( -+ // add 32 so that the final search for a portal frame doesn't load any chunks -+ targetPos, portalSearchRadius + 32, -+ net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY, -+ ca.spottedleaf.concurrentutil.util.Priority.HIGH, -+ (chunks) -> { -+ BlockUtil.FoundRectangle portal = -+ net.minecraft.world.level.block.NetherPortalBlock.findPortalAround(destination, targetPos, destinationBorder, portalSearchRadius); -+ if (portal != null) { -+ portalFound.complete(portal); -+ return; -+ } -+ -+ // add tickets so that we can re-search for a portal once the chunks are loaded -+ Long ticketId = Long.valueOf(CREATE_PORTAL_DOUBLE_CHECK.getAndIncrement()); -+ for (net.minecraft.world.level.chunk.ChunkAccess chunk : chunks) { -+ destination.chunkSource.addTicketAtLevel( -+ TicketType.NETHER_PORTAL_DOUBLE_CHECK, chunk.getPos(), -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, -+ ticketId -+ ); -+ } -+ -+ // no portal found - create one -+ destination.moonrise$loadChunksAsync( -+ targetPos, portalCreateRadius + 32, -+ ca.spottedleaf.concurrentutil.util.Priority.HIGH, -+ (chunks2) -> { -+ // don't need the tickets anymore -+ // note: we expect removeTicketsAtLevel to add an unknown ticket for us automatically -+ // if the ticket level were to decrease -+ for (net.minecraft.world.level.chunk.ChunkAccess chunk : chunks) { -+ destination.chunkSource.removeTicketAtLevel( -+ TicketType.NETHER_PORTAL_DOUBLE_CHECK, chunk.getPos(), -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, -+ ticketId -+ ); -+ } -+ -+ // when two entities portal at the same time, it is possible that both entities reach this -+ // part of the code - and create a double portal -+ // to fix this, we just issue another search to try and see if another entity created -+ // a portal nearby -+ BlockUtil.FoundRectangle existingTryAgain = -+ net.minecraft.world.level.block.NetherPortalBlock.findPortalAround(destination, targetPos, destinationBorder, portalSearchRadius); -+ if (existingTryAgain != null) { -+ portalFound.complete(existingTryAgain); -+ return; -+ } -+ -+ // we do not have the correct entity reference here -+ BlockUtil.FoundRectangle createdPortal = -+ destination.getPortalForcer().createPortal(targetPos, originalPortalDirection, null, portalCreateRadius).orElse(null); -+ // if it wasn't created, passing null is expected here -+ portalFound.complete(createdPortal); -+ } -+ ); -+ } -+ ); -+ break; -+ } -+ default: { -+ throw new IllegalStateException("Unknown portal type " + type); -+ } -+ } -+ } -+ -+ public boolean canPortalAsync(ServerLevel to, boolean considerPassengers) { -+ return this.canPortalAsync(to, considerPassengers, false); -+ } -+ -+ protected boolean canPortalAsync(ServerLevel to, boolean considerPassengers, boolean skipPassengerCheck) { -+ if (considerPassengers) { -+ if (!skipPassengerCheck && this.isPassenger()) { -+ return false; -+ } -+ } else { -+ if (this.isVehicle() || (!skipPassengerCheck && this.isPassenger())) { -+ return false; -+ } -+ } -+ this.getBukkitEntity(); // force bukkit entity to be created before TPing -+ if (!this.canTeleportAsync()) { -+ return false; -+ } -+ if (considerPassengers) { -+ for (Entity entity : this.passengers) { -+ if (!entity.canPortalAsync(to, true, true)) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ protected void prePortalLogic(ServerLevel origin, ServerLevel destination, PortalType type) { -+ -+ } -+ -+ protected boolean portalToAsync(ServerLevel destination, BlockPos portalPos, boolean takePassengers, -+ PortalType type, java.util.function.Consumer teleportComplete) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot portal entity async"); -+ if (!this.canPortalAsync(destination, takePassengers)) { -+ return false; -+ } -+ -+ Vec3 initialPosition = this.position(); -+ ChunkPos initialPositionChunk = new ChunkPos( -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(initialPosition), -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(initialPosition) -+ ); -+ -+ // first, remove entity/passengers from world -+ EntityTreeNode passengerTree = this.detachPassengers(); -+ List fullPassengerTree = passengerTree.getFullTree(); -+ ServerLevel originWorld = (ServerLevel)this.level; -+ -+ for (EntityTreeNode node : fullPassengerTree) { -+ node.root.preChangeDimension(); -+ node.root.prePortalLogic(originWorld, destination, type); -+ } -+ -+ for (EntityTreeNode node : fullPassengerTree) { -+ // we will update pos/rot/speed later -+ node.root = node.root.transformForAsyncTeleport(destination, null, null, null, null); -+ // set portal cooldown -+ node.root.setPortalCooldown(); -+ } -+ -+ // ensure the region is always ticking in case of a shutdown -+ // otherwise, the shutdown will not be able to complete the shutdown as it requires a ticking region -+ Long teleportHoldId = Long.valueOf(TELEPORT_HOLD_TICKET_GEN.getAndIncrement()); -+ originWorld.chunkSource.addTicketAtLevel( -+ TicketType.TELEPORT_HOLD_TICKET, initialPositionChunk, -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, -+ teleportHoldId -+ ); -+ -+ ServerLevel.PendingTeleport beforeFindDestination = new ServerLevel.PendingTeleport(passengerTree, initialPosition); -+ originWorld.pushPendingTeleport(beforeFindDestination); -+ -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable portalInfoCompletable -+ = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); -+ -+ portalInfoCompletable.addWaiter((TeleportTransition info, Throwable throwable) -> { -+ if (!originWorld.removePendingTeleport(beforeFindDestination)) { -+ // the shutdown thread has placed us back into the origin world at the original position -+ // we just have to abandon this teleport to prevent duplication -+ return; -+ } -+ originWorld.chunkSource.removeTicketAtLevel( -+ TicketType.TELEPORT_HOLD_TICKET, initialPositionChunk, -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, -+ teleportHoldId -+ ); -+ // adjust passenger tree to final pos/rot/speed -+ for (EntityTreeNode node : fullPassengerTree) { -+ node.root.transform(info); -+ } -+ -+ // place -+ passengerTree.root.placeInAsync( -+ originWorld, destination, Entity.TELEPORT_FLAG_LOAD_CHUNK | (takePassengers ? Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS : 0L), -+ passengerTree, -+ (Entity teleported) -> { -+ if (info.postTeleportTransition() != null) { -+ info.postTeleportTransition().onTransition(teleported); -+ } -+ -+ if (teleportComplete != null) { -+ teleportComplete.accept(teleported); -+ } -+ } -+ ); -+ }); -+ -+ -+ passengerTree.root.findOrCreatePortalAsync(originWorld, portalPos, destination, type, portalInfoCompletable); -+ -+ return true; -+ } -+ // Folia end - region threading -+ - @Nullable - public Entity teleport(TeleportTransition teleportTransition) { -+ // Folia start - region threading -+ if (true) { -+ throw new UnsupportedOperationException("Must use teleportAsync while in region threading"); -+ } -+ // Folia end - region threading - // Paper start - Fix item duplication and teleport issues - if ((!this.isAlive() || !this.valid) && (teleportTransition.newLevel() != this.level)) { - LOGGER.warn("Illegal Entity Teleport " + this + " to " + teleportTransition.newLevel() + ":" + teleportTransition.position(), new Throwable()); -@@ -3907,6 +_,12 @@ - } - } - -+ // Folia start - region threading - move inventory clearing until after the dimension change -+ protected void postRemoveAfterChangingDimensions() { -+ -+ } -+ // Folia end - region threading - move inventory clearing until after the dimension change -+ - protected void removeAfterChangingDimensions() { - this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION, null); // CraftBukkit - add Bukkit remove cause - if (this instanceof Leashable leashable && leashable.isLeashed()) { // Paper - only call if it is leashed -@@ -4242,6 +_,12 @@ - } - - public void startSeenByPlayer(ServerPlayer serverPlayer) { -+ // Folia start - region threading -+ if (serverPlayer.getCamera() == this) { -+ // set camera again -+ serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCameraPacket(this)); -+ } -+ // Folia end - region threading - } - - public void stopSeenByPlayer(ServerPlayer serverPlayer) { -@@ -4251,6 +_,12 @@ - new io.papermc.paper.event.player.PlayerUntrackEntityEvent(serverPlayer.getBukkitEntity(), this.getBukkitEntity()).callEvent(); - } - // Paper end - entity tracking events -+ // Folia start - region threading -+ if (serverPlayer.getCamera() == this) { -+ // unset camera, the player tick method should TP us close enough again to invoke startSeenByPlayer -+ serverPlayer.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCameraPacket(serverPlayer)); -+ } -+ // Folia end - region threading - } - - public float rotate(Rotation transformRotation) { -@@ -4786,7 +_,8 @@ - } - } - // Paper end - Fix MC-4 -- if (this.position.x != x || this.position.y != y || this.position.z != z) { -+ boolean posChanged = this.position.x != x || this.position.y != y || this.position.z != z; -+ if (posChanged) { // Folia - region threading - synchronized (this.posLock) { // Paper - detailed watchdog information - this.position = new Vec3(x, y, z); - } // Paper - detailed watchdog information -@@ -4805,7 +_,7 @@ - } - // Paper start - Block invalid positions and bounding box; don't allow desync of pos and AABB - // hanging has its own special logic -- if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { -+ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || posChanged)) { - this.setBoundingBox(this.makeBoundingBox()); - } - // Paper end - Block invalid positions and bounding box -@@ -4889,6 +_,12 @@ - return this.removalReason != null; - } - -+ // Folia start - region threading -+ public final boolean hasNullCallback() { -+ return this.levelCallback == EntityInLevelCallback.NULL; -+ } -+ // Folia end - region threading -+ - @Nullable - public Entity.RemovalReason getRemovalReason() { - return this.removalReason; -@@ -4911,6 +_,9 @@ - org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); - // CraftBukkit end - final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers -+ // Folia start - region threading -+ this.preRemove(removalReason); -+ // Folia end - region threading - if (this.removalReason == null) { - this.removalReason = removalReason; - } -@@ -4933,6 +_,10 @@ - public void unsetRemoved() { - this.removalReason = null; - } -+ -+ // Folia start - region threading -+ protected void preRemove(Entity.RemovalReason reason) {} -+ // Folia end - region threading - - // Paper start - Folia schedulers - /** diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch deleted file mode 100644 index 408f1a2..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/LivingEntity.java.patch +++ /dev/null @@ -1,153 +0,0 @@ ---- a/net/minecraft/world/entity/LivingEntity.java -+++ b/net/minecraft/world/entity/LivingEntity.java -@@ -278,7 +_,7 @@ - private Optional lastClimbablePos = Optional.empty(); - @Nullable - private DamageSource lastDamageSource; -- private long lastDamageStamp; -+ private long lastDamageStamp = Long.MIN_VALUE; // Folia - region threading - protected int autoSpinAttackTicks; - protected float autoSpinAttackDmg; - @Nullable -@@ -307,6 +_,26 @@ - return this.getYHeadRot(); - } - // CraftBukkit end -+ // Folia start - region threading -+ @Override -+ public void updateTicks(long fromTickOffset, long fromRedstoneTimeOffset) { -+ super.updateTicks(fromTickOffset, fromRedstoneTimeOffset); -+ if (this.lastDamageStamp != Long.MIN_VALUE) { -+ this.lastDamageStamp += fromRedstoneTimeOffset; -+ } -+ } -+ -+ @Override -+ public boolean canBeSpectated() { -+ return super.canBeSpectated() && this.getHealth() > 0.0F; -+ } -+ -+ @Override -+ protected void resetStoredPositions() { -+ super.resetStoredPositions(); -+ this.lastClimbablePos = Optional.empty(); -+ } -+ // Folia end - region threading - - protected LivingEntity(EntityType entityType, Level level) { - super(entityType, level); -@@ -528,7 +_,7 @@ - - if (this.isDeadOrDying() && this.level().shouldTickDeath(this)) { - this.tickDeath(); -- } -+ } else { this.broadcastedDeath = false; } // Folia - region threading - - if (this.lastHurtByPlayerTime > 0) { - this.lastHurtByPlayerTime--; -@@ -611,11 +_,14 @@ - return true; - } - -+ public boolean broadcastedDeath = false; // Folia - region threading - protected void tickDeath() { - this.deathTime++; - if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) { - this.level().broadcastEntityEvent(this, (byte)60); -- this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause -+ this.broadcastedDeath = true; // Folia - region threading - death has been broadcasted -+ if (!(this instanceof ServerPlayer)) this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause // Folia - region threading - don't remove, we want the tick scheduler to be running -+ if ((this instanceof ServerPlayer)) this.unRide(); // Folia - region threading - unmount player when dead - } - } - -@@ -851,9 +_,9 @@ - } - - this.hurtTime = compound.getShort("HurtTime"); -- this.deathTime = compound.getShort("DeathTime"); -+ this.deathTime = compound.getShort("DeathTime"); this.broadcastedDeath = false; // Folia - region threading - this.lastHurtByMobTimestamp = compound.getInt("HurtByTimestamp"); -- if (compound.contains("Team", 8)) { -+ if (false && compound.contains("Team", 8)) { // Folia start - region threading - String string = compound.getString("Team"); - Scoreboard scoreboard = this.level().getScoreboard(); - PlayerTeam playerTeam = scoreboard.getPlayerTeam(string); -@@ -1115,6 +_,7 @@ - public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause, boolean fireEvent) { - // Paper end - Don't fire sync event during generation - // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API -+ if (!this.hasNullCallback()) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add effects to entities asynchronously"); // Folia - region threading - if (this.isTickingEffects) { - this.effectsToProcess.add(new ProcessableEffect(effectInstance, cause)); - return true; -@@ -1502,7 +_,7 @@ - boolean flag2 = !flag; // CraftBukkit - Ensure to return false if damage is blocked - if (flag2) { - this.lastDamageSource = damageSource; -- this.lastDamageStamp = this.level().getGameTime(); -+ this.lastDamageStamp = this.level().getRedstoneGameTime(); // Folia - region threading - - for (MobEffectInstance mobEffectInstance : this.getActiveEffects()) { - mobEffectInstance.onMobHurt(level, this, damageSource, amount); -@@ -1629,7 +_,7 @@ - - @Nullable - public DamageSource getLastDamageSource() { -- if (this.level().getGameTime() - this.lastDamageStamp > 40L) { -+ if (this.level().getRedstoneGameTime() - this.lastDamageStamp > 40L || this.lastDamageStamp == Long.MIN_VALUE) { // Folia - region threading - this.lastDamageSource = null; - } - -@@ -2420,10 +_,10 @@ - - @Nullable - public LivingEntity getKillCredit() { -- if (this.lastHurtByPlayer != null) { -+ if (this.lastHurtByPlayer != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.lastHurtByPlayer)) { // Folia - region threading - return this.lastHurtByPlayer; - } else { -- return this.lastHurtByMob != null ? this.lastHurtByMob : null; -+ return this.lastHurtByMob != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.lastHurtByMob) ? this.lastHurtByMob : null; // Folia - region threading - } - } - -@@ -2502,7 +_,7 @@ - } - - this.lastDamageSource = damageSource; -- this.lastDamageStamp = this.level().getGameTime(); -+ this.lastDamageStamp = this.level().getRedstoneGameTime(); // Folia - region threading - } - - @Override -@@ -3479,7 +_,7 @@ - this.pushEntities(); - profilerFiller.pop(); - // Paper start - Add EntityMoveEvent -- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof Player)) { -+ if (((ServerLevel) this.level()).getCurrentWorldData().hasEntityMoveEvent && !(this instanceof Player)) { // Folia - region threading - if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { - Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); - Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); -@@ -4152,7 +_,7 @@ - boolean flag = false; - BlockPos blockPos = BlockPos.containing(x, y, z); - Level level = this.level(); -- if (level.hasChunkAt(blockPos)) { -+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((ServerLevel)level, blockPos) && level.hasChunkAt(blockPos)) { // Folia - region threading - boolean flag1 = false; - - while (!flag1 && blockPos.getY() > level.getMinY()) { -@@ -4314,6 +_,11 @@ - 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); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Mob.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Mob.java.patch deleted file mode 100644 index a527d22..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/Mob.java.patch +++ /dev/null @@ -1,61 +0,0 @@ ---- a/net/minecraft/world/entity/Mob.java -+++ b/net/minecraft/world/entity/Mob.java -@@ -254,8 +_,20 @@ - @Nullable - @Override - public LivingEntity getTarget() { -- return this.target; -- } -+ // Folia start - region threading -+ if (this.target != null && (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.target) || this.target.isRemoved())) { -+ this.target = null; -+ return null; -+ } -+ // Folia end - region threading -+ return this.target; -+ } -+ -+ // Folia start - region threading -+ public LivingEntity getTargetRaw() { -+ return this.target; -+ } -+ // Folia end - region threading - - @Nullable - protected final LivingEntity getTargetFromBrain() { -@@ -268,7 +_,7 @@ - } - - public boolean setTarget(LivingEntity target, EntityTargetEvent.TargetReason reason, boolean fireEvent) { -- if (this.getTarget() == target) { -+ if (this.getTargetRaw() == target) { // Folia - region threading - return false; - } - if (fireEvent) { -@@ -1663,12 +_,26 @@ - @Override - protected void removeAfterChangingDimensions() { - super.removeAfterChangingDimensions(); -+ // Folia start - region threading - move inventory clearing until after the dimension change - move into postRemoveAfterChangingDimensions -+// this.getAllSlots().forEach(itemStack -> { -+// if (!itemStack.isEmpty()) { -+// itemStack.setCount(0); -+// } -+// }); -+ // Folia end - region threading - move inventory clearing until after the dimension change - move into postRemoveAfterChangingDimensions -+ } -+ -+ // Folia start - region threading -+ @Override -+ protected void postRemoveAfterChangingDimensions() { -+ super.postRemoveAfterChangingDimensions(); - this.getAllSlots().forEach(itemStack -> { - if (!itemStack.isEmpty()) { - itemStack.setCount(0); - } - }); - } -+ // Folia end - region threading - - @Nullable - @Override diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/PortalProcessor.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/PortalProcessor.java.patch deleted file mode 100644 index 7dd8cd8..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/PortalProcessor.java.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/net/minecraft/world/entity/PortalProcessor.java -+++ b/net/minecraft/world/entity/PortalProcessor.java -@@ -33,6 +_,12 @@ - return this.portal.getPortalDestination(level, entity, this.entryPosition); - } - -+ // Folia start - region threading -+ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget) { -+ return this.portal.portalAsync(sourceWorld, portalTarget, this.entryPosition); -+ } -+ // Folia end - region threading -+ - public Portal.Transition getPortalLocalTransition() { - return this.portal.getLocalTransition(); - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch deleted file mode 100644 index fcf4085..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/TamableAnimal.java.patch +++ /dev/null @@ -1,38 +0,0 @@ ---- a/net/minecraft/world/entity/TamableAnimal.java -+++ b/net/minecraft/world/entity/TamableAnimal.java -@@ -263,6 +_,11 @@ - public void tryToTeleportToOwner() { - LivingEntity owner = this.getOwner(); - if (owner != null) { -+ // Folia start - region threading -+ if (owner.isRemoved() || owner.level() != this.level()) { -+ return; -+ } -+ // Folia end - region threading - this.teleportToAroundBlockPos(owner.blockPosition()); - } - } -@@ -295,7 +_,22 @@ - return false; - } - org.bukkit.Location to = event.getTo(); -- this.moveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); -+ // Folia start - region threading - can't teleport here, we may be removed by teleport logic - delay until next tick -+ // also, use teleportAsync so that crossing region boundaries will not blow up -+ org.bukkit.Location finalTo = to; -+ Level sourceWorld = this.level(); -+ this.getBukkitEntity().taskScheduler.schedule((TamableAnimal nmsEntity) -> { -+ if (nmsEntity.level() == sourceWorld) { -+ nmsEntity.teleportAsync( -+ (net.minecraft.server.level.ServerLevel)nmsEntity.level(), -+ new net.minecraft.world.phys.Vec3(finalTo.getX(), finalTo.getY(), finalTo.getZ()), -+ Float.valueOf(finalTo.getYaw()), Float.valueOf(finalTo.getPitch()), -+ net.minecraft.world.phys.Vec3.ZERO, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN, Entity.TELEPORT_FLAG_LOAD_CHUNK, -+ null -+ ); -+ } -+ }, null, 1L); -+ // Folia end - region threading - can't teleport here, we may be removed by teleport logic - delay until next tick - // CraftBukkit end - this.navigation.stop(); - return true; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/Brain.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/Brain.java.patch deleted file mode 100644 index a92a131..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/Brain.java.patch +++ /dev/null @@ -1,21 +0,0 @@ ---- a/net/minecraft/world/entity/ai/Brain.java -+++ b/net/minecraft/world/entity/ai/Brain.java -@@ -425,9 +_,17 @@ - } - - public void stopAll(ServerLevel level, E owner) { -+ // Folia start - region threading -+ List> behaviors = this.getRunningBehaviors(); -+ if (behaviors.isEmpty()) { -+ // avoid calling getGameTime, as this may be called while portalling an entity - which will cause -+ // the world data retrieval to fail -+ return; -+ } -+ // Folia end - region threading - long gameTime = owner.level().getGameTime(); - -- for (BehaviorControl behaviorControl : this.getRunningBehaviors()) { -+ for (BehaviorControl behaviorControl : behaviors) { // Folia - region threading - behaviorControl.doStop(level, owner, gameTime); - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java.patch deleted file mode 100644 index aee4801..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java -+++ b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java -@@ -46,12 +_,14 @@ - BlockPos blockPos = globalPos.pos(); - ServerLevel level1 = level.getServer().getLevel(globalPos.dimension()); - if (level1 != null) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(level1, blockPos.getX() >> 4, blockPos.getZ() >> 4, () -> { // Folia - region threading - PoiManager poiManager = level1.getPoiManager(); - if (poiManager.exists(blockPos, holder -> true)) { - poiManager.release(blockPos); - } - - DebugPackets.sendPoiTicketCountPacket(level, blockPos); -+ }); // Folia - region threading - } - }); - entity.getBrain().eraseMemory(MemoryModuleType.POTENTIAL_JOB_SITE); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java.patch deleted file mode 100644 index 8522d57..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java -+++ b/net/minecraft/world/entity/ai/behavior/PoiCompetitorScan.java -@@ -19,6 +_,11 @@ - instance, - (jobSite, nearestLivingEntities) -> (level, villager, gameTime) -> { - GlobalPos globalPos = instance.get(jobSite); -+ // Folia start - region threading -+ if (globalPos.dimension() != level.dimension() || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, globalPos.pos())) { -+ return true; -+ } -+ // Folia end - region threading - level.getPoiManager() - .getType(globalPos.pos()) - .ifPresent( diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/YieldJobSite.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/YieldJobSite.java.patch deleted file mode 100644 index d3f8748..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/behavior/YieldJobSite.java.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/net/minecraft/world/entity/ai/behavior/YieldJobSite.java -+++ b/net/minecraft/world/entity/ai/behavior/YieldJobSite.java -@@ -33,7 +_,13 @@ - } else if (villager.getVillagerData().getProfession() != VillagerProfession.NONE) { - return false; - } else { -- BlockPos blockPos = instance.get(potentialJobSite).pos(); -+ // Folia start - region threading -+ GlobalPos globalPos = instance.get(potentialJobSite); -+ BlockPos blockPos = globalPos.pos(); -+ if (globalPos.dimension() != level.dimension() || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, blockPos)) { -+ return true; -+ } -+ // Folia end - region threading - Optional> type = level.getPoiManager().getType(blockPos); - if (type.isEmpty()) { - return true; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch deleted file mode 100644 index 8a9e6bc..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -+++ b/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -@@ -51,7 +_,7 @@ - public boolean canContinueToUse() { - return !this.navigation.isDone() - && !this.tamable.unableToMoveToOwner() -- && !(this.tamable.distanceToSqr(this.owner) <= this.stopDistance * this.stopDistance); -+ && !(this.owner.level() == this.tamable.level() && this.tamable.distanceToSqr(this.owner) <= this.stopDistance * this.stopDistance); // Folia - region threading - check level - } - - @Override diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch deleted file mode 100644 index 62f0121..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java -+++ b/net/minecraft/world/entity/ai/navigation/GroundPathNavigation.java -@@ -42,6 +_,11 @@ - - @Override - public Path createPath(BlockPos pos, @javax.annotation.Nullable Entity entity, int accuracy) { // Paper - EntityPathfindEvent -+ // Folia start - region threading -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level, pos)) { -+ return null; -+ } -+ // Folia end - region threading - LevelChunk chunkNow = this.level.getChunkSource().getChunkNow(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())); - if (chunkNow == null) { - return null; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch deleted file mode 100644 index a992f24..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/navigation/PathNavigation.java.patch +++ /dev/null @@ -1,34 +0,0 @@ ---- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java -+++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java -@@ -96,11 +_,11 @@ - } - - public void recomputePath() { -- if (this.level.getGameTime() - this.timeLastRecompute > 20L) { -+ if (this.tick - this.timeLastRecompute > 20L) { // Folia - region threading - if (this.targetPos != null) { - this.path = null; - this.path = this.createPath(this.targetPos, this.reachRange); -- this.timeLastRecompute = this.level.getGameTime(); -+ this.timeLastRecompute = this.tick; // Folia - region threading - this.hasDelayedRecomputation = false; - } - } else { -@@ -221,7 +_,7 @@ - - public boolean moveTo(Entity entity, double speed) { - // Paper start - Perf: Optimise pathfinding -- if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) { -+ if (this.pathfindFailures > 10 && this.path == null && this.tick < this.lastFailure + 40) { // Folia - region threading - return false; - } - // Paper end - Perf: Optimise pathfinding -@@ -233,7 +_,7 @@ - return true; - } else { - this.pathfindFailures++; -- this.lastFailure = net.minecraft.server.MinecraftServer.currentTick; -+ this.lastFailure = this.tick; // Folia - region threading - return false; - } - // Paper end - Perf: Optimise pathfinding diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/PlayerSensor.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/PlayerSensor.java.patch deleted file mode 100644 index 09962a2..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/PlayerSensor.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -+++ b/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -@@ -22,7 +_,7 @@ - - @Override - protected void doTick(ServerLevel level, LivingEntity entity) { -- List list = level.players() -+ List list = level.getLocalPlayers() // Folia - region threading - .stream() - .filter(EntitySelector.NO_SPECTATORS) - .filter(serverPlayer -> entity.closerThan(serverPlayer, this.getFollowDistance(entity))) diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch deleted file mode 100644 index 8058123..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java -+++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java -@@ -36,7 +_,7 @@ - protected void doTick(ServerLevel level, PathfinderMob entity) { - Brain brain = entity.getBrain(); - TargetingConditions targetingConditions = TEMPT_TARGETING.copy().range((float)entity.getAttributeValue(Attributes.TEMPT_RANGE)); -- List list = level.players() -+ List list = level.getLocalPlayers() // Folia - region threading - .stream() - .filter(EntitySelector.NO_SPECTATORS) - .filter(serverPlayer -> targetingConditions.test(level, entity, serverPlayer)) diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch deleted file mode 100644 index 98576b1..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/village/VillageSiege.java.patch +++ /dev/null @@ -1,134 +0,0 @@ ---- a/net/minecraft/world/entity/ai/village/VillageSiege.java -+++ b/net/minecraft/world/entity/ai/village/VillageSiege.java -@@ -18,68 +_,72 @@ - - public class VillageSiege implements CustomSpawner { - private static final Logger LOGGER = LogUtils.getLogger(); -- private boolean hasSetupSiege; -- private VillageSiege.State siegeState = VillageSiege.State.SIEGE_DONE; -- private int zombiesToSpawn; -- private int nextSpawnTime; -- private int spawnX; -- private int spawnY; -- private int spawnZ; -+ // Folia - region threading - - @Override - public int tick(ServerLevel level, boolean spawnHostiles, boolean spawnPassives) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading -+ // Folia start - region threading -+ // check if the spawn pos is no longer owned by this region -+ if (worldData.villageSiegeState.siegeState != State.SIEGE_DONE -+ && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, worldData.villageSiegeState.spawnX >> 4, worldData.villageSiegeState.spawnZ >> 4, 8)) { -+ // can't spawn here, just re-set -+ worldData.villageSiegeState = new io.papermc.paper.threadedregions.RegionizedWorldData.VillageSiegeState(); -+ } -+ // Folia end - region threading - if (!level.isDay() && spawnHostiles) { - float timeOfDay = level.getTimeOfDay(0.0F); - if (timeOfDay == 0.5) { -- this.siegeState = level.random.nextInt(10) == 0 ? VillageSiege.State.SIEGE_TONIGHT : VillageSiege.State.SIEGE_DONE; -+ worldData.villageSiegeState.siegeState = level.random.nextInt(10) == 0 ? VillageSiege.State.SIEGE_TONIGHT : VillageSiege.State.SIEGE_DONE; // Folia - region threading - } - -- if (this.siegeState == VillageSiege.State.SIEGE_DONE) { -+ if (worldData.villageSiegeState.siegeState == VillageSiege.State.SIEGE_DONE) { // Folia - region threading - return 0; - } else { -- if (!this.hasSetupSiege) { -+ if (!worldData.villageSiegeState.hasSetupSiege) { // Folia - region threading - if (!this.tryToSetupSiege(level)) { - return 0; - } - -- this.hasSetupSiege = true; -+ worldData.villageSiegeState.hasSetupSiege = true; // Folia - region threading - } - -- if (this.nextSpawnTime > 0) { -- this.nextSpawnTime--; -+ if (worldData.villageSiegeState.nextSpawnTime > 0) { // Folia - region threading -+ worldData.villageSiegeState.nextSpawnTime--; // Folia - region threading - return 0; - } else { -- this.nextSpawnTime = 2; -- if (this.zombiesToSpawn > 0) { -+ worldData.villageSiegeState.nextSpawnTime = 2; // Folia - region threading -+ if (worldData.villageSiegeState.zombiesToSpawn > 0) { // Folia - region threading - this.trySpawn(level); -- this.zombiesToSpawn--; -+ worldData.villageSiegeState.zombiesToSpawn--; // Folia - region threading - } else { -- this.siegeState = VillageSiege.State.SIEGE_DONE; -+ worldData.villageSiegeState.siegeState = VillageSiege.State.SIEGE_DONE; // Folia - region threading - } - - return 1; - } - } - } else { -- this.siegeState = VillageSiege.State.SIEGE_DONE; -- this.hasSetupSiege = false; -+ worldData.villageSiegeState.siegeState = VillageSiege.State.SIEGE_DONE; // Folia - region threading -+ worldData.villageSiegeState.hasSetupSiege = false; // Folia - region threading - return 0; - } - } - - private boolean tryToSetupSiege(ServerLevel level) { -- for (Player player : level.players()) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading -+ for (Player player : level.getLocalPlayers()) { // Folia - region threading - if (!player.isSpectator()) { - BlockPos blockPos = player.blockPosition(); - if (level.isVillage(blockPos) && !level.getBiome(blockPos).is(BiomeTags.WITHOUT_ZOMBIE_SIEGES)) { - for (int i = 0; i < 10; i++) { - float f = level.random.nextFloat() * (float) (Math.PI * 2); -- this.spawnX = blockPos.getX() + Mth.floor(Mth.cos(f) * 32.0F); -- this.spawnY = blockPos.getY(); -- this.spawnZ = blockPos.getZ() + Mth.floor(Mth.sin(f) * 32.0F); -- if (this.findRandomSpawnPos(level, new BlockPos(this.spawnX, this.spawnY, this.spawnZ)) != null) { -- this.nextSpawnTime = 0; -- this.zombiesToSpawn = 20; -+ worldData.villageSiegeState.spawnX = blockPos.getX() + Mth.floor(Mth.cos(f) * 32.0F); // Folia - region threading -+ worldData.villageSiegeState.spawnY = blockPos.getY(); // Folia - region threading -+ worldData.villageSiegeState.spawnZ = blockPos.getZ() + Mth.floor(Mth.sin(f) * 32.0F); // Folia - region threading -+ if (this.findRandomSpawnPos(level, new BlockPos(worldData.villageSiegeState.spawnX, worldData.villageSiegeState.spawnY, worldData.villageSiegeState.spawnZ)) != null) { // Folia - region threading -+ worldData.villageSiegeState.nextSpawnTime = 0; // Folia - region threading -+ worldData.villageSiegeState.zombiesToSpawn = 20; // Folia - region threading - break; - } - } -@@ -93,11 +_,13 @@ - } - - private void trySpawn(ServerLevel level) { -- Vec3 vec3 = this.findRandomSpawnPos(level, new BlockPos(this.spawnX, this.spawnY, this.spawnZ)); -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading -+ Vec3 vec3 = this.findRandomSpawnPos(level, new BlockPos(worldData.villageSiegeState.spawnX, worldData.villageSiegeState.spawnY, worldData.villageSiegeState.spawnZ)); // Folia - region threading - if (vec3 != null) { - Zombie zombie; - try { - zombie = new Zombie(level); -+ zombie.moveTo(vec3.x, vec3.y, vec3.z, level.random.nextFloat() * 360.0F, 0.0F); // Folia - region threading - move up - zombie.finalizeSpawn(level, level.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.EVENT, null); - } catch (Exception var5) { - LOGGER.warn("Failed to create zombie for village siege at {}", vec3, var5); -@@ -105,7 +_,7 @@ - return; - } - -- zombie.moveTo(vec3.x, vec3.y, vec3.z, level.random.nextFloat() * 360.0F, 0.0F); -+ //zombie.moveTo(vec3.x, vec3.y, vec3.z, level.random.nextFloat() * 360.0F, 0.0F); // Folia - region threading - move up - level.addFreshEntityWithPassengers(zombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); // CraftBukkit - } - } -@@ -125,7 +_,7 @@ - return null; - } - -- static enum State { -+ public static enum State { // Folia - region threading - SIEGE_CAN_ACTIVATE, - SIEGE_TONIGHT, - SIEGE_DONE; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/village/poi/PoiManager.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/village/poi/PoiManager.java.patch deleted file mode 100644 index eb00cef..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/ai/village/poi/PoiManager.java.patch +++ /dev/null @@ -1,39 +0,0 @@ ---- a/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -58,11 +_,13 @@ - } - - private void updateDistanceTracking(long section) { -+ synchronized (this.villageDistanceTracker) { // Folia - region threading - if (this.isVillageCenter(section)) { - this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); - } else { - this.villageDistanceTracker.removeSource(section); - } -+ } // Folia - region threading - } - - @Override -@@ -347,10 +_,12 @@ - } - - public int sectionsToVillage(SectionPos sectionPos) { -+ synchronized (this.villageDistanceTracker) { // Folia - region threading - // Paper start - rewrite chunk system - this.villageDistanceTracker.propagateUpdates(); - return convertBetweenLevels(this.villageDistanceTracker.getLevel(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(sectionPos))); - // Paper end - rewrite chunk system -+ } // Folia - region threading - } - - boolean isVillageCenter(long chunkPos) { -@@ -364,7 +_,9 @@ - - @Override - public void tick(BooleanSupplier aheadOfTime) { -+ synchronized (this.villageDistanceTracker) { // Folia - region threading - this.villageDistanceTracker.propagateUpdates(); // Paper - rewrite chunk system -+ } // Folia - region threading - } - - @Override diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/animal/Bee.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/animal/Bee.java.patch deleted file mode 100644 index b1317e2..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/animal/Bee.java.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- a/net/minecraft/world/entity/animal/Bee.java -+++ b/net/minecraft/world/entity/animal/Bee.java -@@ -815,6 +_,11 @@ - - @Override - public boolean canBeeUse() { -+ // Folia start - region threading -+ if (Bee.this.hivePos != null && Bee.this.isTooFarAway(Bee.this.hivePos)) { -+ Bee.this.hivePos = null; -+ } -+ // Folia end - region threading - return Bee.this.hivePos != null - && !Bee.this.isTooFarAway(Bee.this.hivePos) - && !Bee.this.hasRestriction() -@@ -925,6 +_,11 @@ - - @Override - public boolean canBeeUse() { -+ // Folia start - region threading -+ if (Bee.this.savedFlowerPos != null && Bee.this.isTooFarAway(Bee.this.savedFlowerPos)) { -+ Bee.this.savedFlowerPos = null; -+ } -+ // Folia end - region threading - return Bee.this.savedFlowerPos != null - && !Bee.this.hasRestriction() - && this.wantsToGoToKnownFlower() diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/animal/Cat.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/animal/Cat.java.patch deleted file mode 100644 index c271e63..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/animal/Cat.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/animal/Cat.java -+++ b/net/minecraft/world/entity/animal/Cat.java -@@ -342,7 +_,7 @@ - TagKey tagKey = flag ? CatVariantTags.FULL_MOON_SPAWNS : CatVariantTags.DEFAULT_SPAWNS; - BuiltInRegistries.CAT_VARIANT.getRandomElementOf(tagKey, level.getRandom()).ifPresent(this::setVariant); - ServerLevel level1 = level.getLevel(); -- if (level1.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK, level).isValid()) { // Paper - Fix swamp hut cat generation deadlock -+ if (level.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK).isValid()) { // Paper - Fix swamp hut cat generation deadlock // Folia - region threading - properly fix this - this.setVariant(BuiltInRegistries.CAT_VARIANT.getOrThrow(CatVariant.ALL_BLACK)); - this.setPersistenceRequired(); - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch deleted file mode 100644 index b392695..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -+++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -@@ -53,7 +_,7 @@ - public void tick() { - this.time++; - this.applyEffectsFromBlocks(); -- this.handlePortal(); -+ //this.handlePortal(); // Folia - region threading - if (this.level() instanceof ServerLevel) { - BlockPos blockPos = this.blockPosition(); - if (((ServerLevel)this.level()).getDragonFight() != null && this.level().getBlockState(blockPos).isAir()) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch deleted file mode 100644 index bb3cd5a..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/decoration/ItemFrame.java.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- a/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -242,7 +_,9 @@ - if (framedMapId != null) { - MapItemSavedData savedData = MapItem.getSavedData(framedMapId, this.level()); - if (savedData != null) { -+ synchronized (savedData) { // Folia - make map data thread-safe - savedData.removedFromFrame(this.pos, this.getId()); -+ } // Folia - make map data thread-safe - } - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch deleted file mode 100644 index f193a8f..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/FallingBlockEntity.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -162,7 +_,7 @@ - return; - } - // Paper end - Configurable falling blocks height nerf -- this.handlePortal(); -+ //this.handlePortal(); // Folia - region threading - if (this.level() instanceof ServerLevel serverLevel && (this.isAlive() || this.forceTickAfterTeleportToDuplicate)) { - BlockPos blockPos = this.blockPosition(); - boolean flag = this.blockState.getBlock() instanceof ConcretePowderBlock; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch deleted file mode 100644 index bcbafd0..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch +++ /dev/null @@ -1,27 +0,0 @@ ---- a/net/minecraft/world/entity/item/ItemEntity.java -+++ b/net/minecraft/world/entity/item/ItemEntity.java -@@ -521,13 +_,21 @@ - return false; - } - -+ // Folia start - region threading -+ @Override -+ public void postChangeDimension() { -+ super.postChangeDimension(); -+ if (!this.level().isClientSide) { -+ this.mergeWithNeighbours(); -+ } -+ } -+ // Folia end - region threading -+ - @Nullable - @Override - public Entity teleport(TeleportTransition teleportTransition) { - Entity entity = super.teleport(teleportTransition); -- if (!this.level().isClientSide && entity instanceof ItemEntity itemEntity) { -- itemEntity.mergeWithNeighbours(); -- } -+ if (entity != null) entity.postChangeDimension(); // Folia - region threading - move to post change - - return entity; - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch deleted file mode 100644 index 8e5206c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/item/PrimedTnt.java.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- a/net/minecraft/world/entity/item/PrimedTnt.java -+++ b/net/minecraft/world/entity/item/PrimedTnt.java -@@ -98,8 +_,8 @@ - - @Override - public void tick() { -- if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot -- this.handlePortal(); -+ if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().getCurrentWorldData().currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot // Folia - region threading -+ //this.handlePortal(); // Folia - region threading - this.applyGravity(); - this.move(MoverType.SELF, this.getDeltaMovement()); - this.applyEffectsFromBlocks(); -@@ -137,7 +_,7 @@ - */ - // Send position and velocity updates to nearby players on every tick while the TNT is in water. - // This does pretty well at keeping their clients in sync with the server. -- net.minecraft.server.level.ChunkMap.TrackedEntity ete = ((net.minecraft.server.level.ServerLevel) this.level()).getChunkSource().chunkMap.entityMap.get(this.getId()); -+ net.minecraft.server.level.ChunkMap.TrackedEntity ete = this.moonrise$getTrackedEntity(); // Folia - region threading - if (ete != null) { - net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket velocityPacket = new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(this); - net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket positionPacket = net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket.teleport(this.getId(), net.minecraft.world.entity.PositionMoveRotation.of(this), java.util.Set.of(), this.onGround); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/monster/Vex.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/monster/Vex.java.patch deleted file mode 100644 index 94ff457..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/monster/Vex.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/monster/Vex.java -+++ b/net/minecraft/world/entity/monster/Vex.java -@@ -349,7 +_,7 @@ - @Override - public void tick() { - BlockPos boundOrigin = Vex.this.getBoundOrigin(); -- if (boundOrigin == null) { -+ if (boundOrigin == null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)Vex.this.level(), boundOrigin)) { // Folia - region threading - boundOrigin = Vex.this.blockPosition(); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch deleted file mode 100644 index c5f8ba6..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/monster/ZombieVillager.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -69,7 +_,7 @@ - @Nullable - private MerchantOffers tradeOffers; - private int villagerXp; -- private int lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit - add field -+ //private int lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit - add field // Folia - region threading - restore original timers - - public ZombieVillager(EntityType entityType, Level level) { - super(entityType, level); -@@ -149,7 +_,7 @@ - } - - super.tick(); -- this.lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit -+ //this.lastTick = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit // Folia - region threading - restore original timers - } - - @Override diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch deleted file mode 100644 index ad8697c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/AbstractVillager.java.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- a/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -218,10 +_,18 @@ - this.readInventoryFromTag(compound, this.registryAccess()); - } - -+ // Folia start - region threading -+ @Override -+ public void preChangeDimension() { -+ super.preChangeDimension(); -+ this.stopTrading(); -+ } -+ // Folia end - region threading -+ - @Nullable - @Override - public Entity teleport(TeleportTransition teleportTransition) { -- this.stopTrading(); -+ this.preChangeDimension(); // Folia - region threading - move into preChangeDimension - return super.teleport(teleportTransition); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch deleted file mode 100644 index 6d84983..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/CatSpawner.java.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- a/net/minecraft/world/entity/npc/CatSpawner.java -+++ b/net/minecraft/world/entity/npc/CatSpawner.java -@@ -18,17 +_,18 @@ - - public class CatSpawner implements CustomSpawner { - private static final int TICK_DELAY = 1200; -- private int nextTick; -+ //private int nextTick; // Folia - region threading - - @Override - public int tick(ServerLevel level, boolean spawnHostiles, boolean spawnPassives) { - if (spawnPassives && level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { -- this.nextTick--; -- if (this.nextTick > 0) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading -+ worldData.catSpawnerNextTick--; // Folia - region threading -+ if (worldData.catSpawnerNextTick > 0) { // Folia - region threading - return 0; - } else { -- this.nextTick = 1200; -- Player randomPlayer = level.getRandomPlayer(); -+ worldData.catSpawnerNextTick = 1200; // Folia - region threading -+ Player randomPlayer = level.getRandomLocalPlayer(); // Folia - region threading - if (randomPlayer == null) { - return 0; - } else { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/Villager.java.patch deleted file mode 100644 index a0ac6d7..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/Villager.java.patch +++ /dev/null @@ -1,28 +0,0 @@ ---- a/net/minecraft/world/entity/npc/Villager.java -+++ b/net/minecraft/world/entity/npc/Villager.java -@@ -246,7 +_,7 @@ - villagerBrain.setCoreActivities(ImmutableSet.of(Activity.CORE)); - villagerBrain.setDefaultActivity(Activity.IDLE); - villagerBrain.setActiveActivityIfPossible(Activity.IDLE); -- villagerBrain.updateActivityFromSchedule(this.level().getDayTime(), this.level().getGameTime()); -+ villagerBrain.updateActivityFromSchedule(this.level().getLevelData().getDayTime(), this.level().getLevelData().getGameTime()); // Folia - region threading - not in the world yet - } - - @Override -@@ -693,6 +_,8 @@ - this.brain.getMemory(moduleType).ifPresent(globalPos -> { - ServerLevel level = server.getLevel(globalPos.dimension()); - if (level != null) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( // Folia - region threading -+ level, globalPos.pos().getX() >> 4, globalPos.pos().getZ() >> 4, () -> { // Folia - region threading - PoiManager poiManager = level.getPoiManager(); - Optional> type = poiManager.getType(globalPos.pos()); - BiPredicate> biPredicate = POI_MEMORIES.get(moduleType); -@@ -700,6 +_,7 @@ - poiManager.release(globalPos.pos()); - DebugPackets.sendPoiTicketCountPacket(level, globalPos.pos()); - } -+ }); // Folia - region threading - } - }); - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch deleted file mode 100644 index 498eb81..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch +++ /dev/null @@ -1,90 +0,0 @@ ---- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -30,16 +_,14 @@ - private static final int SPAWN_CHANCE_INCREASE = 25; - private static final int SPAWN_ONE_IN_X_CHANCE = 10; - private static final int NUMBER_OF_SPAWN_ATTEMPTS = 10; -- private final RandomSource random = RandomSource.create(); -+ private final RandomSource random = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Folia - region threading - private final ServerLevelData serverLevelData; -- private int tickDelay; -- private int spawnDelay; -- private int spawnChance; -+ // Folia - region threading - - public WanderingTraderSpawner(ServerLevelData serverLevelData) { - this.serverLevelData = serverLevelData; - // Paper start - Add Wandering Trader spawn rate config options -- this.tickDelay = Integer.MIN_VALUE; -+ //this.tickDelay = Integer.MIN_VALUE; // Folia - region threading - moved to regionisedworlddata - // this.spawnDelay = serverLevelData.getWanderingTraderSpawnDelay(); - // this.spawnChance = serverLevelData.getWanderingTraderSpawnChance(); - // if (this.spawnDelay == 0 && this.spawnChance == 0) { -@@ -53,35 +_,36 @@ - - @Override - public int tick(ServerLevel level, boolean spawnHostiles, boolean spawnPassives) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading - // Paper start - Add Wandering Trader spawn rate config options -- if (this.tickDelay == Integer.MIN_VALUE) { -- this.tickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -- this.spawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; -- this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; -+ if (worldData.wanderingTraderTickDelay == Integer.MIN_VALUE) { // Folia - region threading -+ worldData.wanderingTraderTickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; // Folia - region threading -+ worldData.wanderingTraderSpawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; // Folia - region threading -+ worldData.wanderingTraderSpawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; // Folia - region threading - } - if (!level.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { - return 0; -- } else if (--this.tickDelay - 1 > 0) { -- this.tickDelay = this.tickDelay - 1; -+ } else if (--worldData.wanderingTraderTickDelay - 1 > 0) { // Folia - region threading -+ worldData.wanderingTraderTickDelay = worldData.wanderingTraderTickDelay - 1; // Folia - region threading - return 0; - } else { -- this.tickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -- this.spawnDelay = this.spawnDelay - level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ worldData.wanderingTraderTickDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; // Folia - region threading -+ worldData.wanderingTraderSpawnDelay = worldData.wanderingTraderSpawnDelay - level.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; // Folia - region threading - //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways -- if (this.spawnDelay > 0) { -+ if (worldData.wanderingTraderSpawnDelay > 0) { // Folia - region threading - return 0; - } else { -- this.spawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; -+ worldData.wanderingTraderSpawnDelay = level.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; // Folia - region threading - if (!level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { - return 0; - } else { -- int i = this.spawnChance; -- this.spawnChance = Mth.clamp(this.spawnChance + level.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); -+ int i = worldData.wanderingTraderSpawnChance; // Folia - region threading -+ worldData.wanderingTraderSpawnChance = Mth.clamp(worldData.wanderingTraderSpawnChance + level.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); // Folia - region threading - //this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways - if (this.random.nextInt(100) > i) { - return 0; - } else if (this.spawn(level)) { -- this.spawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; -+ worldData.wanderingTraderSpawnChance = level.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; // Folia - region threading - // Paper end - Add Wandering Trader spawn rate config options - return 1; - } else { -@@ -93,7 +_,7 @@ - } - - private boolean spawn(ServerLevel serverLevel) { -- Player randomPlayer = serverLevel.getRandomPlayer(); -+ Player randomPlayer = serverLevel.getRandomLocalPlayer(); // Folia - region threading - if (randomPlayer == null) { - return true; - } else if (this.random.nextInt(10) != 0) { -@@ -116,7 +_,7 @@ - this.tryToSpawnLlamaFor(serverLevel, wanderingTrader, 4); - } - -- this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID()); -+ //this.serverLevelData.setWanderingTraderId(wanderingTrader.getUUID()); // Folia - region threading - doesn't appear to be used anywhere, so avoid the race condition here... - // wanderingTrader.setDespawnDelay(48000); // Paper - moved above, modifiable by plugins on CreatureSpawnEvent - wanderingTrader.setWanderTarget(blockPos1); - wanderingTrader.restrictTo(blockPos1, 16); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch deleted file mode 100644 index bdbe979..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/player/Player.java.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/net/minecraft/world/entity/player/Player.java -+++ b/net/minecraft/world/entity/player/Player.java -@@ -1506,6 +_,14 @@ - } - } - -+ // Folia start - region threading -+ @Override -+ protected void preRemove(RemovalReason reason) { -+ super.preRemove(reason); -+ this.fishing = null; -+ } -+ // Folia end - region threading -+ - public boolean isLocalPlayer() { - return false; - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch deleted file mode 100644 index 6ec6a75..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractArrow.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -176,6 +_,11 @@ - - @Override - public void tick() { -+ // Folia start - region threading - make sure entities do not move into regions they do not own -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { -+ return; -+ } -+ // Folia end - region threading - make sure entities do not move into regions they do not own - boolean flag = !this.isNoPhysics(); - Vec3 deltaMovement = this.getDeltaMovement(); - BlockPos blockPos = this.blockPosition(); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch deleted file mode 100644 index bf3ab2b..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java -+++ b/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java -@@ -80,6 +_,11 @@ - this.setPos(location); - this.applyEffectsFromBlocks(); - super.tick(); -+ // Folia start - region threading - make sure entities do not move into regions they do not own -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { -+ return; -+ } -+ // Folia end - region threading - make sure entities do not move into regions they do not own - if (this.shouldBurn()) { - this.igniteForSeconds(1.0F); - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch deleted file mode 100644 index e46ad4b..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -+++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -@@ -130,6 +_,11 @@ - } - }); - } -+ // Folia start - region threading -+ if (this.attachedToEntity != null && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.attachedToEntity)) { -+ this.attachedToEntity = null; -+ } -+ // Folia end - region threading - - if (this.attachedToEntity != null) { - Vec3 handHoldingItemAngle; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch deleted file mode 100644 index 3baf552..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FishingHook.java.patch +++ /dev/null @@ -1,50 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/FishingHook.java -+++ b/net/minecraft/world/entity/projectile/FishingHook.java -@@ -94,7 +_,7 @@ - - public FishingHook(Player player, Level level, int luck, int lureSpeed) { - this(EntityType.FISHING_BOBBER, level, luck, lureSpeed); -- this.setOwner(player); -+ //this.setOwner(player); // Folia - region threading - move this down after position so that thread-checks do not fail - float xRot = player.getXRot(); - float yRot = player.getYRot(); - float cos = Mth.cos(-yRot * (float) (Math.PI / 180.0) - (float) Math.PI); -@@ -105,6 +_,7 @@ - double eyeY = player.getEyeY(); - double d1 = player.getZ() - cos * 0.3; - this.moveTo(d, eyeY, d1, yRot, xRot); -+ this.setOwner(player); // Folia - region threading - move this down after position so that thread-checks do not fail - Vec3 vec3 = new Vec3(-sin, Mth.clamp(-(sin1 / f), -5.0F, 5.0F), -cos); - double len = vec3.length(); - vec3 = vec3.multiply( -@@ -260,6 +_,11 @@ - } - - private boolean shouldStopFishing(Player player) { -+ // Folia start - region threading -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) { -+ return true; -+ } -+ // Folia end - region threading - ItemStack mainHandItem = player.getMainHandItem(); - ItemStack offhandItem = player.getOffhandItem(); - boolean isFishingRod = mainHandItem.is(Items.FISHING_ROD); -@@ -623,9 +_,17 @@ - @Override - public void remove(Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.Cause cause) { - // CraftBukkit end -- this.updateOwnerInfo(null); -+ //this.updateOwnerInfo(null); // Folia - region threading - move into preRemove - super.remove(reason, cause); // CraftBukkit - add Bukkit remove cause - } -+ -+ // Folia start - region threading -+ @Override -+ protected void preRemove(RemovalReason reason) { -+ super.preRemove(reason); -+ this.updateOwnerInfo(null); -+ } -+ // Folia end - region threading - - @Override - public void onClientRemoval() { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch deleted file mode 100644 index 738b678..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/LlamaSpit.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/LlamaSpit.java -+++ b/net/minecraft/world/entity/projectile/LlamaSpit.java -@@ -41,6 +_,11 @@ - @Override - public void tick() { - super.tick(); -+ // Folia start - region threading - make sure entities do not move into regions they do not own -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { -+ return; -+ } -+ // Folia end - region threading - make sure entities do not move into regions they do not own - Vec3 deltaMovement = this.getDeltaMovement(); - HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); - this.preHitTargetOrDeflectSelf(hitResultOnMoveVector); // CraftBukkit - projectile hit event diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch deleted file mode 100644 index 5dfe796..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/Projectile.java.patch +++ /dev/null @@ -1,87 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/Projectile.java -+++ b/net/minecraft/world/entity/projectile/Projectile.java -@@ -38,7 +_,7 @@ - @Nullable - public UUID ownerUUID; - @Nullable -- public Entity cachedOwner; -+ public org.bukkit.craftbukkit.entity.CraftEntity cachedOwner; // Folia - region threading - replace with CraftEntity - public boolean leftOwner; - public boolean hasBeenShot; - @Nullable -@@ -52,7 +_,7 @@ - public void setOwner(@Nullable Entity owner) { - if (owner != null) { - this.ownerUUID = owner.getUUID(); -- this.cachedOwner = owner; -+ this.cachedOwner = owner.getBukkitEntity(); // Folia - region threading - } - // Paper start - Refresh ProjectileSource for projectiles - else { -@@ -69,22 +_,38 @@ - if (fillCache) { - this.getOwner(); - } -- if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof org.bukkit.projectiles.ProjectileSource projSource) { -+ if (this.cachedOwner != null && !this.cachedOwner.getHandleRaw().isRemoved() && this.projectileSource == null && this.cachedOwner instanceof org.bukkit.projectiles.ProjectileSource projSource) { // Folia - region threading - this.projectileSource = projSource; - } - } - // Paper end - Refresh ProjectileSource for projectiles - -+ // Folia start - region threading -+ // In general, this is an entire mess. At the time of writing, there are fifty usages of getOwner. -+ // Usage of this function is to avoid concurrency issues, even if it sacrifices behavior. - @Nullable - @Override - public Entity getOwner() { -- if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) { -+ Entity ret = this.getOwnerRaw(); -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(ret) && (ret == null || !ret.isRemoved()) ? ret : null; -+ } -+ // Folia end - region threading -+ -+ @Nullable -+ public Entity getOwnerRaw() { // Folia - region threading -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot update owner state asynchronously"); // Folia - region threading -+ if (this.cachedOwner != null && !this.cachedOwner.isPurged()) { // Folia - region threading - this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles -- return this.cachedOwner; -+ return this.cachedOwner.getHandleRaw(); // Folia - region threading - } else if (this.ownerUUID != null) { -- this.cachedOwner = this.findOwner(this.ownerUUID); -+ // Folia start - region threading -+ Entity ret = this.findOwner(this.ownerUUID); -+ if (ret != null) { -+ this.cachedOwner = ret.getBukkitEntity(); -+ } -+ // Folia end - region threading - this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles -- return this.cachedOwner; -+ return ret; // Folia - region threading - } else { - return null; - } -@@ -130,7 +_,12 @@ - protected void setOwnerThroughUUID(UUID uuid) { - if (this.ownerUUID != uuid) { - this.ownerUUID = uuid; -- this.cachedOwner = this.findOwner(uuid); -+ // Folia start - region threading -+ Entity cachedOwner = this.findOwner(this.ownerUUID); -+ if (cachedOwner != null) { -+ this.cachedOwner = cachedOwner.getBukkitEntity(); -+ } -+ // Folia end - region threading - } - } - -@@ -454,7 +_,7 @@ - @Override - public boolean mayInteract(ServerLevel level, BlockPos pos) { - Entity owner = this.getOwner(); -- return owner instanceof Player ? owner.mayInteract(level, pos) : owner == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ return owner instanceof Player && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(owner) ? owner.mayInteract(level, pos) : owner == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Folia - region threading - } - - public boolean mayBreak(ServerLevel level) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch deleted file mode 100644 index ac75249..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/SmallFireball.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/SmallFireball.java -+++ b/net/minecraft/world/entity/projectile/SmallFireball.java -@@ -24,7 +_,7 @@ - public SmallFireball(Level level, LivingEntity owner, Vec3 movement) { - super(EntityType.SMALL_FIREBALL, owner, movement, level); - // CraftBukkit start -- if (this.getOwner() != null && this.getOwner() instanceof Mob) { -+ if (owner != null && this.getOwner() != null && this.getOwner() instanceof Mob) { // Folia - region threading - this.isIncendiary = (level instanceof ServerLevel serverLevel) && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); - } - // CraftBukkit end diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch deleted file mode 100644 index cf0996c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/ThrowableProjectile.java -+++ b/net/minecraft/world/entity/projectile/ThrowableProjectile.java -@@ -43,6 +_,11 @@ - - @Override - public void tick() { -+ // Folia start - region threading - make sure entities do not move into regions they do not own -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { -+ return; -+ } -+ // Folia end - region threading - make sure entities do not move into regions they do not own - this.handleFirstTickBubbleColumn(); - this.applyGravity(); - this.applyInertia(); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch deleted file mode 100644 index d07e2f3..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch +++ /dev/null @@ -1,140 +0,0 @@ ---- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -+++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -@@ -58,15 +_,11 @@ - } - - private void deregisterFromCurrentOwner() { -- if (this.getOwner() instanceof ServerPlayer serverPlayer) { -- serverPlayer.deregisterEnderPearl(this); -- } -+ // Folia - region threading - we remove the registration logic, we do not need to fetch the owner - } - - private void registerToCurrentOwner() { -- if (this.getOwner() instanceof ServerPlayer serverPlayer) { -- serverPlayer.registerEnderPearl(this); -- } -+ // Folia - region threading - we remove the registration logic, we do not need to fetch the owner - } - - @Nullable -@@ -99,6 +_,81 @@ - result.getEntity().hurt(this.damageSources().thrown(this, this.getOwner()), 0.0F); - } - -+ // Folia start - region threading -+ private static void attemptTeleport(Entity source, ServerLevel checkWorld, net.minecraft.world.phys.Vec3 to) { -+ final boolean onPortalCooldown = source.isOnPortalCooldown(); -+ // ignore retired callback, in those cases we do not want to teleport -+ source.getBukkitEntity().taskScheduler.schedule( -+ (Entity entity) -> { -+ if (!isAllowedToTeleportOwner(entity, checkWorld)) { -+ return; -+ } -+ // source is now an invalid reference, do not use it, use the entity parameter -+ net.minecraft.world.phys.Vec3 endermitePos = entity.position(); -+ -+ // dismount from any vehicles, so we can teleport and to prevent desync -+ if (entity.isPassenger()) { -+ entity.unRide(); -+ } -+ -+ if (onPortalCooldown) { -+ entity.setPortalCooldown(); -+ } -+ -+ entity.teleportAsync( -+ checkWorld, to, null, null, null, -+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL, -+ // chunk could have been unloaded -+ Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS | Entity.TELEPORT_FLAG_LOAD_CHUNK, -+ (Entity teleported) -> { -+ // entity is now an invalid reference, do not use it, instead use teleported -+ if (teleported instanceof ServerPlayer player) { -+ // connection teleport is already done -+ ServerLevel world = player.serverLevel(); -+ -+ // endermite spawn chance -+ if (world.random.nextFloat() < 0.05F && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { -+ Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(world, EntitySpawnReason.TRIGGERED); -+ -+ if (entityendermite != null) { -+ float yRot = teleported.getYRot(); -+ float xRot = teleported.getXRot(); -+ Runnable spawn = () -> { -+ entityendermite.moveTo(endermitePos.x, endermitePos.y, endermitePos.z, yRot, xRot); -+ world.addFreshEntity(entityendermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL); -+ }; -+ -+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(world, endermitePos, net.minecraft.world.phys.Vec3.ZERO, 1)) { -+ spawn.run(); -+ } else { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( -+ world, -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkCoordinate(endermitePos.x), -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkCoordinate(endermitePos.z), -+ spawn -+ ); -+ } -+ } -+ } -+ -+ // damage player -+ teleported.resetFallDistance(); -+ player.resetCurrentImpulseContext(); -+ player.hurtServer(player.serverLevel(), player.damageSources().enderPearl().eventEntityDamager(player), 5.0F); // CraftBukkit // Paper - fix DamageSource API -+ playSound(teleported.level(), to); -+ } else { -+ // reset fall damage so that if the entity was falling they do not instantly die -+ teleported.resetFallDistance(); -+ playSound(teleported.level(), to); -+ } -+ } -+ ); -+ }, -+ null, 1L -+ ); -+ } -+ // Folia end - region threading -+ - @Override - protected void onHit(HitResult result) { - super.onHit(result); -@@ -117,6 +_,20 @@ - } - - if (this.level() instanceof ServerLevel serverLevel && !this.isRemoved()) { -+ // Folia start - region threading -+ if (true) { -+ // we can't fire events, because we do not actually know where the other entity is located -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this)) { -+ throw new IllegalStateException("Must be on tick thread for ticking entity: " + this); -+ } -+ Entity entity = this.getOwnerRaw(); -+ if (entity != null) { -+ attemptTeleport(entity, (ServerLevel)this.level(), this.position()); -+ } -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); -+ return; -+ } -+ // Folia end - region threading - Entity owner = this.getOwner(); - if (owner != null && isAllowedToTeleportOwner(owner, serverLevel)) { - if (owner.isPassenger()) { -@@ -212,7 +_,15 @@ - } - } - -- private void playSound(Level level, Vec3 pos) { -+ // Folia start - region threading -+ @Override -+ public void preChangeDimension() { -+ super.preChangeDimension(); -+ // Don't change the owner here, since the tick logic will consider it anyways. -+ } -+ // Folia end - region threading -+ -+ private static void playSound(Level level, Vec3 pos) { // Folia - region threading - static - level.playSound(null, pos.x, pos.y, pos.z, SoundEvents.PLAYER_TELEPORT, SoundSource.PLAYERS); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raid.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raid.java.patch deleted file mode 100644 index aadaf34..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raid.java.patch +++ /dev/null @@ -1,61 +0,0 @@ ---- a/net/minecraft/world/entity/raid/Raid.java -+++ b/net/minecraft/world/entity/raid/Raid.java -@@ -110,6 +_,13 @@ - public final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer(PDC_TYPE_REGISTRY); - // Paper end - -+ // Folia start - make raids thread-safe -+ public boolean ownsRaid() { -+ BlockPos center = this.getCenter(); -+ return center != null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, center.getX() >> 4, center.getZ() >> 4, 8); -+ } -+ // Folia end - make raids thread-safe -+ - public Raid(int id, ServerLevel level, BlockPos center) { - this.id = id; - this.level = level; -@@ -207,7 +_,7 @@ - private Predicate validPlayer() { - return player -> { - BlockPos blockPos = player.blockPosition(); -- return player.isAlive() && this.level.getRaidAt(blockPos) == this; -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player) && player.isAlive() && this.level.getRaidAt(blockPos) == this; // Folia - make raids thread-safe - }; - } - -@@ -384,14 +_,21 @@ - if (entity instanceof LivingEntity) { - LivingEntity livingEntity = (LivingEntity)entity; - if (!entity.isSpectator()) { -- livingEntity.addEffect( -- new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true) -- ); -+ //livingEntity.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true)); // Folia start - Fix off region raid heroes - moved down - if (livingEntity instanceof ServerPlayer serverPlayer) { -- serverPlayer.awardStat(Stats.RAID_WIN); -- CriteriaTriggers.RAID_WIN.trigger(serverPlayer); -+ //serverPlayer.awardStat(Stats.RAID_WIN); // Folia start - Fix off region raid heroes - moved down -+ //CriteriaTriggers.RAID_WIN.trigger(serverPlayer); // Folia start - Fix off region raid heroes - moved down - winners.add(serverPlayer.getBukkitEntity()); // CraftBukkit - } -+ // Folia start - Fix off region raid heroes -+ livingEntity.getBukkitEntity().taskScheduler.schedule((LivingEntity lv) -> { -+ lv.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true)); -+ if (lv instanceof ServerPlayer serverPlayer) { -+ serverPlayer.awardStat(Stats.RAID_WIN); -+ CriteriaTriggers.RAID_WIN.trigger(serverPlayer); -+ } -+ }, null, 1L); -+ // Folia end - Fix off region raid heroes - } - } - } -@@ -496,7 +_,7 @@ - Collection players = this.raidEvent.getPlayers(); - long randomLong = this.random.nextLong(); - -- for (ServerPlayer serverPlayer : this.level.players()) { -+ for (ServerPlayer serverPlayer : this.level.getLocalPlayers()) { // Folia - region threading - Vec3 vec3 = serverPlayer.position(); - Vec3 vec31 = Vec3.atCenterOf(pos); - double squareRoot = Math.sqrt((vec31.x - vec3.x) * (vec31.x - vec3.x) + (vec31.z - vec3.z) * (vec31.z - vec3.z)); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raider.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raider.java.patch deleted file mode 100644 index e766fc7..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raider.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/raid/Raider.java -+++ b/net/minecraft/world/entity/raid/Raider.java -@@ -86,7 +_,7 @@ - Raid currentRaid = this.getCurrentRaid(); - if (this.canJoinRaid()) { - if (currentRaid == null) { -- if (this.level().getGameTime() % 20L == 0L) { -+ if (this.level().getRedstoneGameTime() % 20L == 0L) { // Folia - region threading - Raid raidAt = ((ServerLevel)this.level()).getRaidAt(this.blockPosition()); - if (raidAt != null && Raids.canJoinRaid(this, raidAt)) { - raidAt.joinRaid(raidAt.getGroupsSpawned(), this, null, true); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raids.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raids.java.patch deleted file mode 100644 index b58e170..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raids.java.patch +++ /dev/null @@ -1,119 +0,0 @@ ---- a/net/minecraft/world/entity/raid/Raids.java -+++ b/net/minecraft/world/entity/raid/Raids.java -@@ -25,9 +_,9 @@ - - public class Raids extends SavedData { - private static final String RAID_FILE_ID = "raids"; -- public final Map raidMap = Maps.newHashMap(); -+ public final Map raidMap = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - make raids thread-safe - private final ServerLevel level; -- private int nextAvailableID; -+ private final java.util.concurrent.atomic.AtomicInteger nextAvailableID = new java.util.concurrent.atomic.AtomicInteger(); // Folia - make raids thread-safe - private int tick; - - public static SavedData.Factory factory(ServerLevel level) { -@@ -36,7 +_,7 @@ - - public Raids(ServerLevel level) { - this.level = level; -- this.nextAvailableID = 1; -+ this.nextAvailableID.set(1); // Folia - make raids thread-safe - this.setDirty(); - } - -@@ -44,12 +_,25 @@ - return this.raidMap.get(id); - } - -+ // Folia start - make raids thread-safe -+ public void globalTick() { -+ ++this.tick; -+ if (this.tick % 200 == 0) { -+ this.setDirty(); -+ } -+ } -+ - public void tick() { -- this.tick++; -+ // Folia end - make raids thread-safe - Iterator iterator = this.raidMap.values().iterator(); - - while (iterator.hasNext()) { - Raid raid = iterator.next(); -+ // Folia start - make raids thread-safe -+ if (!raid.ownsRaid()) { -+ continue; -+ } -+ // Folia end - make raids thread-safe - if (this.level.getGameRules().getBoolean(GameRules.RULE_DISABLE_RAIDS)) { - raid.stop(); - } -@@ -62,14 +_,17 @@ - } - } - -- if (this.tick % 200 == 0) { -- this.setDirty(); -- } -+ // Folia - make raids thread-safe - move to globalTick() - - DebugPackets.sendRaids(this.level, this.raidMap.values()); - } - - public static boolean canJoinRaid(Raider raider, Raid raid) { -+ // Folia start - make raids thread-safe -+ if (!raid.ownsRaid()) { -+ return false; -+ } -+ // Folia end - make raids thread-safe - return raider != null - && raid != null - && raid.getLevel() != null -@@ -87,7 +_,7 @@ - return null; - } else { - DimensionType dimensionType = player.level().dimensionType(); -- if (!dimensionType.hasRaids()) { -+ if (!dimensionType.hasRaids() || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player) || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, pos.getX() >> 4, pos.getZ() >> 4, 8) || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, player.chunkPosition().x, player.chunkPosition().z, 8)) { // Folia - region threading - return null; - } else { - List list = this.level -@@ -145,7 +_,7 @@ - - public static Raids load(ServerLevel level, CompoundTag tag) { - Raids raids = new Raids(level); -- raids.nextAvailableID = tag.getInt("NextAvailableID"); -+ raids.nextAvailableID.set(tag.getInt("NextAvailableID")); // Folia - make raids thread-safe - raids.tick = tag.getInt("Tick"); - ListTag list = tag.getList("Raids", 10); - -@@ -160,7 +_,7 @@ - - @Override - public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { -- tag.putInt("NextAvailableID", this.nextAvailableID); -+ tag.putInt("NextAvailableID", this.nextAvailableID.get()); // Folia - make raids thread-safe - tag.putInt("Tick", this.tick); - ListTag listTag = new ListTag(); - -@@ -179,7 +_,7 @@ - } - - private int getUniqueId() { -- return ++this.nextAvailableID; -+ return this.nextAvailableID.incrementAndGet(); // Folia - make raids thread-safe - } - - @Nullable -@@ -188,6 +_,11 @@ - double d = distance; - - for (Raid raid1 : this.raidMap.values()) { -+ // Folia start - make raids thread-safe -+ if (!raid1.ownsRaid()) { -+ continue; -+ } -+ // Folia end - make raids thread-safe - double d1 = raid1.getCenter().distSqr(pos); - if (raid1.isActive() && d1 < d) { - raid = raid1; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch deleted file mode 100644 index 7988197..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java -+++ b/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java -@@ -145,5 +_,11 @@ - return net.minecraft.world.entity.vehicle.MinecartCommandBlock.this.getBukkitEntity(); - } - // CraftBukkit end -+ // Folia start -+ @Override -+ public void threadCheck() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(MinecartCommandBlock.this, "Asynchronous sendSystemMessage to a command block"); -+ } -+ // Folia end - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/MinecartHopper.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/MinecartHopper.java.patch deleted file mode 100644 index 148301c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/MinecartHopper.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/entity/vehicle/MinecartHopper.java -+++ b/net/minecraft/world/entity/vehicle/MinecartHopper.java -@@ -145,7 +_,7 @@ - - // Paper start - public void immunize() { -- this.activatedImmunityTick = Math.max(this.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 20); -+ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 20); - } - // Paper end - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch deleted file mode 100644 index 12c7a2b..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch +++ /dev/null @@ -1,128 +0,0 @@ ---- a/net/minecraft/world/item/ItemStack.java -+++ b/net/minecraft/world/item/ItemStack.java -@@ -386,31 +_,32 @@ - DataComponentPatch previousPatch = this.components.asPatch(); - int oldCount = this.getCount(); - ServerLevel serverLevel = (ServerLevel) context.getLevel(); -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = serverLevel.getCurrentWorldData(); // Folia - region threading - - if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement -- serverLevel.captureBlockStates = true; -+ worldData.captureBlockStates = true; // Folia - region threading - // special case bonemeal - if (item == Items.BONE_MEAL) { -- serverLevel.captureTreeGeneration = true; -+ worldData.captureTreeGeneration = true; // Folia - region threading - } - } - InteractionResult interactionResult; - try { - interactionResult = item.useOn(context); - } finally { -- serverLevel.captureBlockStates = false; -+ worldData.captureBlockStates = false; // Folia - region threading - } - DataComponentPatch newPatch = this.components.asPatch(); - int newCount = this.getCount(); - this.setCount(oldCount); - this.restorePatch(previousPatch); -- if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) { -- serverLevel.captureTreeGeneration = false; -+ if (interactionResult.consumesAction() && worldData.captureTreeGeneration && !worldData.capturedBlockStates.isEmpty()) { // Folia - region threading -+ worldData.captureTreeGeneration = false; // Folia - region threading - org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld()); -- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; -- net.minecraft.world.level.block.SaplingBlock.treeType = null; -- List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); -- serverLevel.capturedBlockStates.clear(); -+ 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 -+ List 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) { - boolean isBonemeal = this.getItem() == Items.BONE_MEAL; -@@ -436,15 +_,15 @@ - player.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat - } - -- SignItem.openSign = null; // SPIGOT-6758 - Reset on early return -+ SignItem.openSign.set(null); // SPIGOT-6758 - Reset on early return // Folia - region threading - return interactionResult; - } -- serverLevel.captureTreeGeneration = false; -+ worldData.captureTreeGeneration = false; // Folia - region threading - if (player != null && interactionResult instanceof InteractionResult.Success success && success.wasItemInteraction()) { - InteractionHand hand = context.getHand(); - org.bukkit.event.block.BlockPlaceEvent placeEvent = null; -- List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); -- serverLevel.capturedBlockStates.clear(); -+ List blocks = new java.util.ArrayList<>(worldData.capturedBlockStates.values()); // Folia - region threading -+ worldData.capturedBlockStates.clear(); // Folia - region threading - if (blocks.size() > 1) { - placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos.getX(), clickedPos.getY(), clickedPos.getZ()); - } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement -@@ -455,17 +_,17 @@ - interactionResult = InteractionResult.FAIL; // cancel placement - // PAIL: Remove this when MC-99075 fixed - placeEvent.getPlayer().updateInventory(); -- serverLevel.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot -+ worldData.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot // Folia - region threading - // revert back all captured blocks -- serverLevel.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 -- serverLevel.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent -+ worldData.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 // Folia - region threading -+ worldData.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent // Folia - region threading - for (org.bukkit.block.BlockState blockstate : blocks) { - blockstate.update(true, false); - } -- serverLevel.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent -- serverLevel.preventPoiUpdated = false; -+ worldData.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent // Folia - region threading -+ worldData.preventPoiUpdated = false; // Folia - region threading - -- SignItem.openSign = null; // SPIGOT-6758 - Reset on early return -+ SignItem.openSign.set(null); // SPIGOT-6758 - Reset on early return // Folia - region threading - } else { - // Change the stack to its new contents if it hasn't been tampered with. - if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), previousPatch)) { -@@ -473,7 +_,7 @@ - this.setCount(newCount); - } - -- for (java.util.Map.Entry e : serverLevel.capturedTileEntities.entrySet()) { -+ for (java.util.Map.Entry e : worldData.capturedTileEntities.entrySet()) { // Folia - region threading - serverLevel.setBlockEntity(e.getValue()); - } - -@@ -508,15 +_,15 @@ - } - - // SPIGOT-4678 -- if (this.item instanceof SignItem && SignItem.openSign != null) { -+ if (this.item instanceof SignItem && SignItem.openSign.get() != null) { // Folia - region threading - try { -- if (serverLevel.getBlockEntity(SignItem.openSign) instanceof net.minecraft.world.level.block.entity.SignBlockEntity blockEntity) { -- if (serverLevel.getBlockState(SignItem.openSign).getBlock() instanceof net.minecraft.world.level.block.SignBlock signBlock) { -+ if (serverLevel.getBlockEntity(SignItem.openSign.get()) instanceof net.minecraft.world.level.block.entity.SignBlockEntity blockEntity) { // Folia - region threading -+ if (serverLevel.getBlockState(SignItem.openSign.get()).getBlock() instanceof net.minecraft.world.level.block.SignBlock signBlock) { // Folia - region threading - signBlock.openTextEdit(player, blockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // CraftBukkit // Paper - Add PlayerOpenSignEvent - } - } - } finally { -- SignItem.openSign = null; -+ SignItem.openSign.set(null); - } - } - -@@ -544,8 +_,8 @@ - player.awardStat(Stats.ITEM_USED.get(item)); - } - } -- serverLevel.capturedTileEntities.clear(); -- serverLevel.capturedBlockStates.clear(); -+ worldData.capturedTileEntities.clear(); // Folia - region threading -+ worldData.capturedBlockStates.clear(); // Folia - region threading - // CraftBukkit end - - return interactionResult; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/item/MapItem.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/item/MapItem.java.patch deleted file mode 100644 index 61acbd4..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/item/MapItem.java.patch +++ /dev/null @@ -1,61 +0,0 @@ ---- a/net/minecraft/world/item/MapItem.java -+++ b/net/minecraft/world/item/MapItem.java -@@ -70,6 +_,7 @@ - } - - public void update(Level level, Entity viewer, MapItemSavedData data) { -+ synchronized (data) { // Folia - make map data thread-safe - if (level.dimension() == data.dimension && viewer instanceof Player) { - int i = 1 << data.scale; - int i1 = data.centerX; -@@ -99,8 +_,8 @@ - int i9 = (i1 / i + i6 - 64) * i; - int i10 = (i2 / i + i7 - 64) * i; - Multiset multiset = LinkedHashMultiset.create(); -- LevelChunk chunk = level.getChunkIfLoaded(SectionPos.blockToSectionCoord(i9), SectionPos.blockToSectionCoord(i10)); // Paper - Maps shouldn't load chunks -- if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks -+ LevelChunk chunk = level.getChunkIfLoaded(SectionPos.blockToSectionCoord(i9), SectionPos.blockToSectionCoord(i10)); // Paper - Maps shouldn't load chunks // Folia - super important that it uses getChunkIfLoaded -+ if (chunk != null && !chunk.isEmpty() && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, chunk.getPos())) { // Paper - Maps shouldn't load chunks // Folia - make sure chunk is owned - int i11 = 0; - double d1 = 0.0; - if (level.dimensionType().hasCeiling()) { -@@ -182,6 +_,7 @@ - } - } - } -+ } // Folia - make map data thread-safe - } - - private BlockState getCorrectStateForFluidBlock(Level level, BlockState state, BlockPos pos) { -@@ -196,6 +_,7 @@ - public static void renderBiomePreviewMap(ServerLevel serverLevel, ItemStack stack) { - MapItemSavedData savedData = getSavedData(stack, serverLevel); - if (savedData != null) { -+ synchronized (savedData) { // Folia - make map data thread-safe - if (serverLevel.dimension() == savedData.dimension) { - int i = 1 << savedData.scale; - int i1 = savedData.centerX; -@@ -265,6 +_,7 @@ - } - } - } -+ } // Folia - make map data thread-safe - } - } - -@@ -273,6 +_,7 @@ - if (!level.isClientSide) { - MapItemSavedData savedData = getSavedData(stack, level); - if (savedData != null) { -+ synchronized (savedData) { // Folia - region threading - if (entity instanceof Player player) { - savedData.tickCarriedBy(player, stack); - } -@@ -280,6 +_,7 @@ - if (!savedData.locked && (isSelected || entity instanceof Player && ((Player)entity).getOffhandItem() == stack)) { - this.update(level, entity, savedData); - } -+ } // Folia - region threading - } - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/item/SignItem.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/item/SignItem.java.patch deleted file mode 100644 index c165b42..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/item/SignItem.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/net/minecraft/world/item/SignItem.java -+++ b/net/minecraft/world/item/SignItem.java -@@ -11,7 +_,7 @@ - import net.minecraft.world.level.block.state.BlockState; - - public class SignItem extends StandingAndWallBlockItem { -- public static BlockPos openSign; // CraftBukkit -+ public static final ThreadLocal openSign = new ThreadLocal<>(); // CraftBukkit // Folia - region threading - public SignItem(Block standingBlock, Block wallBlock, Item.Properties properties) { - super(standingBlock, wallBlock, Direction.DOWN, properties); - } -@@ -30,7 +_,7 @@ - && level.getBlockState(pos).getBlock() instanceof SignBlock signBlock) { - // CraftBukkit start - SPIGOT-4678 - // signBlock.openTextEdit(player, signBlockEntity, true); -- SignItem.openSign = pos; -+ SignItem.openSign.set(pos); // Folia - region threading - // CraftBukkit end - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch deleted file mode 100644 index 32f635e..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/item/component/LodestoneTracker.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/item/component/LodestoneTracker.java -+++ b/net/minecraft/world/item/component/LodestoneTracker.java -@@ -29,7 +_,10 @@ - return this; - } else { - BlockPos blockPos = this.target.get().pos(); -- return level.isInWorldBounds(blockPos) && (!level.hasChunkAt(blockPos) || level.getPoiManager().existsAtPosition(PoiTypes.LODESTONE, blockPos)) // Paper - Prevent compass from loading chunks -+ // Folia start - do not access the POI data off-region -+ net.minecraft.world.level.chunk.LevelChunk chunk = level.getChunkIfLoaded(blockPos); -+ return level.isInWorldBounds(blockPos) && (chunk == null || chunk.getBlockState(blockPos).getBlock() == net.minecraft.world.level.block.Blocks.LODESTONE) // Paper - Prevent compass from loading chunks -+ // Folia end - do not access the POI data off-region - ? this - : new LodestoneTracker(Optional.empty(), true); - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch deleted file mode 100644 index dca7751..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch +++ /dev/null @@ -1,35 +0,0 @@ ---- a/net/minecraft/world/level/BaseCommandBlock.java -+++ b/net/minecraft/world/level/BaseCommandBlock.java -@@ -21,7 +_,7 @@ - import net.minecraft.world.phys.Vec3; - - public abstract class BaseCommandBlock implements CommandSource { -- private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); -+ private static final ThreadLocal TIME_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss")); // Folia - region threading - SDF is not thread-safe - private static final Component DEFAULT_NAME = Component.literal("@"); - private long lastExecution = -1L; - private boolean updateLastExecution = true; -@@ -114,6 +_,7 @@ - } - - public boolean performCommand(Level level) { -+ if (true) return false; // Folia - region threading - if (level.isClientSide || level.getGameTime() == this.lastExecution) { - return false; - } else if ("Searge".equalsIgnoreCase(this.command)) { -@@ -164,11 +_,14 @@ - this.customName = customName; - } - -+ public void threadCheck() {} // Folia -+ - @Override - public void sendSystemMessage(Component component) { - if (this.trackOutput) { - org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks -- this.lastOutput = Component.literal("[" + TIME_FORMAT.format(new Date()) + "] ").append(component); -+ this.threadCheck(); // Folia -+ this.lastOutput = Component.literal("[" + TIME_FORMAT.get().format(new Date()) + "] ").append(component); // Folia - region threading - SDF is not thread-safe - this.onUpdated(); - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/EntityGetter.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/EntityGetter.java.patch deleted file mode 100644 index 1d68066..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/EntityGetter.java.patch +++ /dev/null @@ -1,61 +0,0 @@ ---- a/net/minecraft/world/level/EntityGetter.java -+++ b/net/minecraft/world/level/EntityGetter.java -@@ -24,6 +_,12 @@ - return this.getEntities(EntityTypeTest.forClass(entityClass), area, filter); - } - -+ // Folia start - region threading -+ default List getLocalPlayers() { -+ return java.util.Collections.emptyList(); -+ } -+ // Folia end - region threading -+ - List players(); - - default List getEntities(@Nullable Entity entity, AABB area) { -@@ -123,7 +_,7 @@ - double d = -1.0; - Player player = null; - -- for (Player player1 : this.players()) { -+ for (Player player1 : this.getLocalPlayers()) { // Folia - region threading - if (predicate == null || predicate.test(player1)) { - double d1 = player1.distanceToSqr(x, y, z); - if ((distance < 0.0 || d1 < distance * distance) && (d == -1.0 || d1 < d)) { -@@ -144,7 +_,7 @@ - default List findNearbyBukkitPlayers(double x, double y, double z, double radius, @Nullable Predicate predicate) { - com.google.common.collect.ImmutableList.Builder builder = com.google.common.collect.ImmutableList.builder(); - -- for (Player human : this.players()) { -+ for (Player human : this.getLocalPlayers()) { // Folia - region threading - if (predicate == null || predicate.test(human)) { - double distanceSquared = human.distanceToSqr(x, y, z); - -@@ -171,7 +_,7 @@ - - // Paper start - Affects Spawning API - default boolean hasNearbyAlivePlayerThatAffectsSpawning(double x, double y, double z, double range) { -- for (Player player : this.players()) { -+ for (Player player : this.getLocalPlayers()) { // Folia - region threading - if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player)) { // combines NO_SPECTATORS and LIVING_ENTITY_STILL_ALIVE with an "affects spawning" check - double distanceSqr = player.distanceToSqr(x, y, z); - if (range < 0.0D || distanceSqr < range * range) { -@@ -184,7 +_,7 @@ - // Paper end - Affects Spawning API - - default boolean hasNearbyAlivePlayer(double x, double y, double z, double distance) { -- for (Player player : this.players()) { -+ for (Player player : this.getLocalPlayers()) { // Folia - region threading - if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { - double d = player.distanceToSqr(x, y, z); - if (distance < 0.0 || d < distance * distance) { -@@ -198,8 +_,7 @@ - - @Nullable - default Player getPlayerByUUID(UUID uniqueId) { -- for (int i = 0; i < this.players().size(); i++) { -- Player player = this.players().get(i); -+ for (Player player : this.getLocalPlayers()) { // Folia - region threading - if (uniqueId.equals(player.getUUID())) { - return player; - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch deleted file mode 100644 index 3b5dbac..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch +++ /dev/null @@ -1,404 +0,0 @@ ---- a/net/minecraft/world/level/Level.java -+++ b/net/minecraft/world/level/Level.java -@@ -115,10 +_,10 @@ - public static final int TICKS_PER_DAY = 24000; - public static final int MAX_ENTITY_SPAWN_Y = 20000000; - public static final int MIN_ENTITY_SPAWN_Y = -20000000; -- public final List blockEntityTickers = Lists.newArrayList(); // Paper - public -- protected final NeighborUpdater neighborUpdater; -- private final List pendingBlockEntityTickers = Lists.newArrayList(); -- private boolean tickingBlockEntities; -+ //public final List blockEntityTickers = Lists.newArrayList(); // Paper - public // Folia - region threading -+ public final int neighbourUpdateMax; //protected final NeighborUpdater neighborUpdater; // Folia - region threading -+ //private final List pendingBlockEntityTickers = Lists.newArrayList(); // Folia - region threading -+ //private boolean tickingBlockEntities; // Folia - region threading - public final Thread thread; - private final boolean isDebug; - private int skyDarken; -@@ -128,7 +_,7 @@ - public float rainLevel; - protected float oThunderLevel; - public float thunderLevel; -- public final RandomSource random = new ca.spottedleaf.moonrise.common.util.ThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // Paper - replace random -+ public final RandomSource random = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Paper - replace random // Folia - region threading - @Deprecated - private final RandomSource threadSafeRandom = RandomSource.createThreadSafe(); - private final Holder dimensionTypeRegistration; -@@ -139,28 +_,17 @@ - private final ResourceKey dimension; - private final RegistryAccess registryAccess; - private final DamageSources damageSources; -- private long subTickCount; -+ private final java.util.concurrent.atomic.AtomicLong subTickCount = new java.util.concurrent.atomic.AtomicLong(); //private long subTickCount; // Folia - region threading - - // CraftBukkit start Added the following - private final CraftWorld world; - public boolean pvpMode; - public org.bukkit.generator.ChunkGenerator generator; - -- 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 Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper -- public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates -- public List captureDrops; -+ // Folia - region threading - moved to regionised data - public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); -- // Paper start -- public int wakeupInactiveRemainingAnimals; -- public int wakeupInactiveRemainingFlying; -- public int wakeupInactiveRemainingMonsters; -- public int wakeupInactiveRemainingVillagers; -- // Paper end -- public boolean populating; -+ // Folia - region threading - moved to regionised data -+ // Folia - region threading - public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot - // Paper start - add paper world config - private final io.papermc.paper.configuration.WorldConfiguration paperConfig; -@@ -173,9 +_,9 @@ - public static BlockPos lastPhysicsProblem; // Spigot - private org.spigotmc.TickLimiter entityLimiter; - private org.spigotmc.TickLimiter tileLimiter; -- private int tileTickPosition; -- public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions -- public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here -+ //private int tileTickPosition; // Folia - region threading -+ //public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions // Folia - region threading -+ //public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here // Folia - region threading - - public CraftWorld getWorld() { - return this.world; -@@ -825,6 +_,32 @@ - return chunk != null ? chunk.getNoiseBiome(x, y, z) : this.getUncachedNoiseBiome(x, y, z); - } - // Paper end - optimise random ticking -+ // Folia start - region ticking -+ public final io.papermc.paper.threadedregions.RegionizedData worldRegionData -+ = new io.papermc.paper.threadedregions.RegionizedData<>( -+ (ServerLevel)this, () -> new io.papermc.paper.threadedregions.RegionizedWorldData((ServerLevel)Level.this), -+ io.papermc.paper.threadedregions.RegionizedWorldData.REGION_CALLBACK -+ ); -+ public volatile io.papermc.paper.threadedregions.RegionizedServer.WorldLevelData tickData; -+ public final java.util.concurrent.ConcurrentHashMap.KeySetView needsChangeBroadcasting = java.util.concurrent.ConcurrentHashMap.newKeySet(); -+ -+ public io.papermc.paper.threadedregions.RegionizedWorldData getCurrentWorldData() { -+ final io.papermc.paper.threadedregions.RegionizedWorldData ret = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); -+ if (ret == null) { -+ return ret; -+ } -+ Level world = ret.world; -+ if (world != this) { -+ throw new IllegalStateException("World mismatch: expected " + this.getWorld().getName() + " but got " + world.getWorld().getName()); -+ } -+ return ret; -+ } -+ -+ @Override -+ public List getLocalPlayers() { -+ return this.getCurrentWorldData().getLocalPlayers(); -+ } -+ // Folia end - region ticking - - protected Level( - WritableLevelData levelData, -@@ -888,7 +_,7 @@ - this.thread = Thread.currentThread(); - this.biomeManager = new BiomeManager(this, biomeZoomSeed); - this.isDebug = isDebug; -- this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates); -+ this.neighbourUpdateMax = maxChainedNeighborUpdates; // Folia - region threading - this.registryAccess = registryAccess; - this.damageSources = new DamageSources(registryAccess); - -@@ -1035,8 +_,8 @@ - @Nullable - public final BlockState getBlockStateIfLoaded(BlockPos pos) { - // CraftBukkit start - tree generation -- if (this.captureTreeGeneration) { -- CraftBlockState previous = this.capturedBlockStates.get(pos); -+ if (this.getCurrentWorldData().captureTreeGeneration) { // Folia - region threading -+ CraftBlockState previous = this.getCurrentWorldData().capturedBlockStates.get(pos); // Folia - region threading - if (previous != null) { - return previous.getHandle(); - } -@@ -1098,16 +_,18 @@ - - @Override - public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // Folia - region threading -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.getCurrentWorldData(); // Folia - region threading - // CraftBukkit start - tree generation -- if (this.captureTreeGeneration) { -+ if (worldData.captureTreeGeneration) { // Folia - region threading - // Paper start - Protect Bedrock and End Portal/Frames from being destroyed - BlockState type = getBlockState(pos); - if (!type.isDestroyable()) return false; - // Paper end - Protect Bedrock and End Portal/Frames from being destroyed -- CraftBlockState blockstate = this.capturedBlockStates.get(pos); -+ CraftBlockState blockstate = worldData.capturedBlockStates.get(pos); // Folia - region threading - if (blockstate == null) { - blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); -- this.capturedBlockStates.put(pos.immutable(), blockstate); -+ worldData.capturedBlockStates.put(pos.immutable(), blockstate); // Folia - region threading - } - blockstate.setData(state); - blockstate.setFlag(flags); -@@ -1123,10 +_,10 @@ - Block block = state.getBlock(); - // CraftBukkit start - capture blockstates - boolean captured = false; -- if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) { -+ if (worldData.captureBlockStates && !worldData.capturedBlockStates.containsKey(pos)) { // Folia - region threading - CraftBlockState blockstate = (CraftBlockState) world.getBlockAt(pos.getX(), pos.getY(), pos.getZ()).getState(); // Paper - use CB getState to get a suitable snapshot - blockstate.setFlag(flags); // Paper - set flag -- this.capturedBlockStates.put(pos.immutable(), blockstate); -+ worldData.capturedBlockStates.put(pos.immutable(), blockstate); // Folia - region threading - captured = true; - } - // CraftBukkit end -@@ -1136,8 +_,8 @@ - - if (blockState == null) { - // CraftBukkit start - remove blockstate if failed (or the same) -- if (this.captureBlockStates && captured) { -- this.capturedBlockStates.remove(pos); -+ if (worldData.captureBlockStates && captured) { // Folia - region threading -+ worldData.capturedBlockStates.remove(pos); // Folia - region threading - } - // CraftBukkit end - return false; -@@ -1174,7 +_,7 @@ - */ - - // CraftBukkit start -- if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates -+ if (!worldData.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates // Folia - region threading - // Modularize client and physic updates - // Spigot start - try { -@@ -1219,7 +_,7 @@ - iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam - CraftWorld world = ((ServerLevel) this).getWorld(); - boolean cancelledUpdates = false; // Paper - Fix block place logic -- if (world != null && ((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent -+ if (world != null && ((ServerLevel)this).getCurrentWorldData().hasPhysicsEvent) { // Paper - BlockPhysicsEvent // Folia - region threading - BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); - this.getCraftServer().getPluginManager().callEvent(event); - -@@ -1233,7 +_,7 @@ - } - - // CraftBukkit start - SPIGOT-5710 -- if (!this.preventPoiUpdated) { -+ if (!this.getCurrentWorldData().preventPoiUpdated) { // Folia - region threading - this.onBlockStateChange(blockposition, iblockdata1, iblockdata2); - } - // CraftBukkit end -@@ -1322,7 +_,7 @@ - - @Override - public void neighborShapeChanged(Direction direction, BlockPos pos, BlockPos neighborPos, BlockState neighborState, int flags, int recursionLeft) { -- this.neighborUpdater.shapeUpdate(direction, neighborState, pos, neighborPos, flags, recursionLeft); -+ this.getCurrentWorldData().neighborUpdater.shapeUpdate(direction, neighborState, pos, neighborPos, flags, recursionLeft); // Folia - region threading - } - - @Override -@@ -1346,11 +_,34 @@ - return this.getChunkSource().getLightEngine(); - } - -+ // Folia start - region threading -+ @Nullable -+ public BlockState getBlockStateFromEmptyChunkIfLoaded(BlockPos pos) { -+ net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)this.getChunkSource(); -+ ChunkAccess chunk = chunkProvider.getChunkAtImmediately(pos.getX() >> 4, pos.getZ() >> 4); -+ if (chunk != null) { -+ return chunk.getBlockState(pos); -+ } -+ return null; -+ } -+ -+ @Nullable -+ public BlockState getBlockStateFromEmptyChunk(BlockPos pos) { -+ net.minecraft.server.level.ServerChunkCache chunkProvider = (net.minecraft.server.level.ServerChunkCache)this.getChunkSource(); -+ ChunkAccess chunk = chunkProvider.getChunkAtImmediately(pos.getX() >> 4, pos.getZ() >> 4); -+ if (chunk != null) { -+ return chunk.getBlockState(pos); -+ } -+ chunk = chunkProvider.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.EMPTY, true); -+ return chunk.getBlockState(pos); -+ } -+ // Folia end - region threading -+ - @Override - public BlockState getBlockState(BlockPos pos) { - // CraftBukkit start - tree generation -- if (this.captureTreeGeneration) { -- CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper -+ if (this.getCurrentWorldData().captureTreeGeneration) { // Folia - region threading -+ CraftBlockState previous = this.getCurrentWorldData().capturedBlockStates.get(pos); // Paper // Folia - region threading - if (previous != null) { - return previous.getHandle(); - } -@@ -1454,17 +_,16 @@ - } - - public void addBlockEntityTicker(TickingBlockEntity ticker) { -- (this.tickingBlockEntities ? this.pendingBlockEntityTickers : this.blockEntityTickers).add(ticker); -+ ((ServerLevel)this).getCurrentWorldData().addBlockEntityTicker(ticker); // Folia - regionised ticking - } - - protected void tickBlockEntities() { - ProfilerFiller profilerFiller = Profiler.get(); - profilerFiller.push("blockEntities"); -- this.tickingBlockEntities = true; -- if (!this.pendingBlockEntityTickers.isEmpty()) { -- this.blockEntityTickers.addAll(this.pendingBlockEntityTickers); -- this.pendingBlockEntityTickers.clear(); -- } -+ final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - regionised ticking -+ regionizedWorldData.seTtickingBlockEntities(true); // Folia - regionised ticking -+ regionizedWorldData.pushPendingTickingBlockEntities(); // Folia - regionised ticking -+ List blockEntityTickers = regionizedWorldData.getBlockEntityTickers(); // Folia - regionised ticking - - // Spigot start - boolean runsNormally = this.tickRateManager().runsNormally(); -@@ -1472,9 +_,8 @@ - int tickedEntities = 0; // Paper - rewrite chunk system - var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet(); // Paper - Fix MC-117075; use removeAll - toRemove.add(null); // Paper - Fix MC-117075 -- for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters -- this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0; -- TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition); -+ for (int i = 0; i < blockEntityTickers.size(); i++) { // Paper - Disable tick limiters // Folia - regionised ticking -+ TickingBlockEntity tickingBlockEntity = blockEntityTickers.get(i); // Folia - regionised ticking - // Spigot end - if (tickingBlockEntity.isRemoved()) { - toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll -@@ -1487,11 +_,11 @@ - // Paper end - rewrite chunk system - } - } -- this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 -+ blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 // Folia - regionised ticking - -- this.tickingBlockEntities = false; -+ regionizedWorldData.seTtickingBlockEntities(false); // Folia - regionised ticking - profilerFiller.pop(); -- this.spigotConfig.currentPrimedTnt = 0; // Spigot -+ regionizedWorldData.currentPrimedTnt = 0; // Spigot // Folia - region threading - } - - public void guardEntityTick(Consumer consumerEntity, T entity) { -@@ -1502,7 +_,8 @@ - final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); - MinecraftServer.LOGGER.error(msg, var6); - getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, var6))); // Paper - ServerExceptionEvent -- entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ if (!(entity instanceof net.minecraft.server.level.ServerPlayer)) entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Folia - properly disconnect players -+ if (entity instanceof net.minecraft.server.level.ServerPlayer player) player.connection.disconnect(net.minecraft.network.chat.Component.translatable("multiplayer.disconnect.generic"), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Folia - properly disconnect players - // Paper end - Prevent block entity and entity crashes - } - this.moonrise$midTickTasks(); // Paper - rewrite chunk system -@@ -1648,9 +_,14 @@ - - @Nullable - public BlockEntity getBlockEntity(BlockPos pos, boolean validate) { -+ // Folia start - region threading -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { -+ return null; -+ } -+ // Folia end - region threading - // Paper start - Perf: Optimize capturedTileEntities lookup - net.minecraft.world.level.block.entity.BlockEntity blockEntity; -- if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { -+ if (!this.getCurrentWorldData().capturedTileEntities.isEmpty() && (blockEntity = this.getCurrentWorldData().capturedTileEntities.get(pos)) != null) { // Folia - region threading - return blockEntity; - } - // Paper end - Perf: Optimize capturedTileEntities lookup -@@ -1668,8 +_,8 @@ - BlockPos blockPos = blockEntity.getBlockPos(); - if (!this.isOutsideBuildHeight(blockPos)) { - // CraftBukkit start -- if (this.captureBlockStates) { -- this.capturedTileEntities.put(blockPos.immutable(), blockEntity); -+ if (this.getCurrentWorldData().captureBlockStates) { // Folia - region threading -+ this.getCurrentWorldData().capturedTileEntities.put(blockPos.immutable(), blockEntity); // Folia - region threading - return; - } - // CraftBukkit end -@@ -1749,6 +_,7 @@ - - @Override - public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // Folia - region threading - Profiler.get().incrementCounter("getEntities"); - List list = Lists.newArrayList(); - -@@ -1778,6 +_,7 @@ - public void getEntities(final EntityTypeTest entityTypeTest, - final AABB boundingBox, final Predicate predicate, - final List into, final int maxCount) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, boundingBox, "Cannot getEntities asynchronously"); // Folia - region threading - Profiler.get().incrementCounter("getEntities"); - - if (entityTypeTest instanceof net.minecraft.world.entity.EntityType byType) { -@@ -1877,13 +_,34 @@ - public void disconnect() { - } - -+ @Override // Folia - region threading - public long getGameTime() { -- return this.levelData.getGameTime(); -+ // Folia start - region threading -+ // Dumb world gen thread calls this for some reason. So, check for null. -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.getCurrentWorldData(); -+ return worldData == null ? this.getLevelData().getGameTime() : worldData.getTickData().nonRedstoneGameTime(); -+ // Folia end - region threading - } - - public long getDayTime() { -- return this.levelData.getDayTime(); -- } -+ // Folia start - region threading -+ // Dumb world gen thread calls this for some reason. So, check for null. -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.getCurrentWorldData(); -+ return worldData == null ? this.getLevelData().getDayTime() : worldData.getTickData().dayTime(); -+ // Folia end - region threading -+ } -+ -+ // Folia start - region threading -+ @Override -+ public long dayTime() { -+ return this.getDayTime(); -+ } -+ -+ @Override -+ public long getRedstoneGameTime() { -+ return this.getCurrentWorldData().getRedstoneGameTime(); -+ } -+ // Folia end - region threading - - public boolean mayInteract(Player player, BlockPos pos) { - return true; -@@ -2061,8 +_,7 @@ - public abstract RecipeAccess recipeAccess(); - - public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) { -- this.randValue = this.randValue * 3 + 1013904223; -- int i = this.randValue >> 2; -+ int i = this.random.nextInt() >> 2; // Folia - region threading - return new BlockPos(x + (i & 15), y + (i >> 16 & yMask), z + (i >> 8 & 15)); - } - -@@ -2083,7 +_,7 @@ - - @Override - public long nextSubTickCount() { -- return this.subTickCount++; -+ return this.subTickCount.getAndIncrement(); // Folia - region threading - } - - @Override diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/LevelAccessor.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/LevelAccessor.java.patch deleted file mode 100644 index a0ecb20..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/LevelAccessor.java.patch +++ /dev/null @@ -1,29 +0,0 @@ ---- a/net/minecraft/world/level/LevelAccessor.java -+++ b/net/minecraft/world/level/LevelAccessor.java -@@ -33,14 +_,24 @@ - - long nextSubTickCount(); - -+ // Folia start - region threading -+ default long getGameTime() { -+ return this.getLevelData().getGameTime(); -+ } -+ -+ default long getRedstoneGameTime() { -+ return this.getLevelData().getGameTime(); -+ } -+ // Folia end - region threading -+ - @Override - default ScheduledTick createTick(BlockPos pos, T type, int delay, TickPriority priority) { -- return new ScheduledTick<>(type, pos, this.getLevelData().getGameTime() + delay, priority, this.nextSubTickCount()); -+ return new ScheduledTick<>(type, pos, this.getRedstoneGameTime() + delay, priority, this.nextSubTickCount()); // Folia - region threading - } - - @Override - default ScheduledTick createTick(BlockPos pos, T type, int delay) { -- return new ScheduledTick<>(type, pos, this.getLevelData().getGameTime() + delay, this.nextSubTickCount()); -+ return new ScheduledTick<>(type, pos, this.getRedstoneGameTime() + delay, this.nextSubTickCount()); // Folia - region threading - } - - LevelData getLevelData(); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/LevelReader.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/LevelReader.java.patch deleted file mode 100644 index 79def9c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/LevelReader.java.patch +++ /dev/null @@ -1,28 +0,0 @@ ---- a/net/minecraft/world/level/LevelReader.java -+++ b/net/minecraft/world/level/LevelReader.java -@@ -204,6 +_,25 @@ - return toY >= this.getMinY() && fromY <= this.getMaxY() && this.hasChunksAt(fromX, fromZ, toX, toZ); - } - -+ // Folia start - region threading -+ default boolean hasAndOwnsChunksAt(int minX, int minZ, int maxX, int maxZ) { -+ int i = SectionPos.blockToSectionCoord(minX); -+ int j = SectionPos.blockToSectionCoord(maxX); -+ int k = SectionPos.blockToSectionCoord(minZ); -+ int l = SectionPos.blockToSectionCoord(maxZ); -+ -+ for(int m = i; m <= j; ++m) { -+ for(int n = k; n <= l; ++n) { -+ if (!this.hasChunk(m, n) || (this instanceof net.minecraft.server.level.ServerLevel world && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(world, m, n))) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ // Folia end - region threading -+ - @Deprecated - default boolean hasChunksAt(int fromX, int fromZ, int toX, int toZ) { - int sectionPosCoord = SectionPos.blockToSectionCoord(fromX); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch deleted file mode 100644 index 165c607..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/NaturalSpawner.java -+++ b/net/minecraft/world/level/NaturalSpawner.java -@@ -137,7 +_,7 @@ - int limit = mobCategory.getMaxInstancesPerChunk(); - SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory); - if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { -- spawnThisTick = level.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(spawnCategory) == 0; -+ spawnThisTick = level.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && level.getRedstoneGameTime() % level.ticksPerSpawnCategory.getLong(spawnCategory) == 0; // Folia - region threading - limit = level.getWorld().getSpawnLimit(spawnCategory); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch deleted file mode 100644 index d2ade28..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/ServerExplosion.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/net/minecraft/world/level/ServerExplosion.java -+++ b/net/minecraft/world/level/ServerExplosion.java -@@ -773,17 +_,18 @@ - if (!this.level.paperConfig().environment.optimizeExplosions) { - return this.getSeenFraction(vec3d, entity, this.directMappedBlockCache, this.mutablePos); // Paper - collision optimisations - } -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.level.getCurrentWorldData(); // Folia - region threading - CacheKey key = new CacheKey(this, entity.getBoundingBox()); -- Float blockDensity = this.level.explosionDensityCache.get(key); -+ Float blockDensity = worldData.explosionDensityCache.get(key); // Folia - region threading - if (blockDensity == null) { - blockDensity = this.getSeenFraction(vec3d, entity, this.directMappedBlockCache, this.mutablePos); // Paper - collision optimisations -- this.level.explosionDensityCache.put(key, blockDensity); -+ worldData.explosionDensityCache.put(key, blockDensity); // Folia - region threading - } - - return blockDensity; - } - -- static class CacheKey { -+ public static class CacheKey { // Folia - region threading - public - private final Level world; - private final double posX, posY, posZ; - private final double minX, minY, minZ; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch deleted file mode 100644 index baae667..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/ServerLevelAccessor.java.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/net/minecraft/world/level/ServerLevelAccessor.java -+++ b/net/minecraft/world/level/ServerLevelAccessor.java -@@ -6,6 +_,12 @@ - public interface ServerLevelAccessor extends LevelAccessor { - ServerLevel getLevel(); - -+ // Folia start - region threading -+ default public StructureManager structureManager() { -+ throw new UnsupportedOperationException(); -+ } -+ // Folia end - region threading -+ - default void addFreshEntityWithPassengers(Entity entity) { - // CraftBukkit start - this.addFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/StructureManager.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/StructureManager.java.patch deleted file mode 100644 index 890ce2e..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/StructureManager.java.patch +++ /dev/null @@ -1,48 +0,0 @@ ---- a/net/minecraft/world/level/StructureManager.java -+++ b/net/minecraft/world/level/StructureManager.java -@@ -48,12 +_,7 @@ - } - - public List startsForStructure(ChunkPos chunkPos, Predicate structurePredicate) { -- // Paper start - Fix swamp hut cat generation deadlock -- return this.startsForStructure(chunkPos, structurePredicate, null); -- } -- -- public List startsForStructure(ChunkPos chunkPos, Predicate structurePredicate, @Nullable ServerLevelAccessor levelAccessor) { -- Map allReferences = (levelAccessor == null ? this.level : levelAccessor).getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); -+ Map allReferences = this.level.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); // Folia - region threading - // Paper end - Fix swamp hut cat generation deadlock - Builder builder = ImmutableList.builder(); - -@@ -124,20 +_,12 @@ - } - - public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate) { -- // Paper start - Fix swamp hut cat generation deadlock -- return this.getStructureWithPieceAt(pos, predicate, null); -- } -- -- public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey tag, @Nullable ServerLevelAccessor levelAccessor) { -- return this.getStructureWithPieceAt(pos, structure -> structure.is(tag), levelAccessor); -- } -- -- public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate> predicate, @Nullable ServerLevelAccessor levelAccessor) { -+ // Folia - region threading - // Paper end - Fix swamp hut cat generation deadlock - Registry registry = this.registryAccess().lookupOrThrow(Registries.STRUCTURE); - - for (StructureStart structureStart : this.startsForStructure( -- new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false), levelAccessor // Paper - Fix swamp hut cat generation deadlock -+ new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false) // Paper - Fix swamp hut cat generation deadlock // Folia - region threading - )) { - if (this.structureHasPieceAt(pos, structureStart)) { - return structureStart; -@@ -182,7 +_,7 @@ - } - - public void addReference(StructureStart structureStart) { -- structureStart.addReference(); -+ //structureStart.addReference(); // Folia - region threading - move to caller - this.structureCheck.incrementReference(structureStart.getChunkPos(), structureStart.getStructure()); - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/BedBlock.java.patch deleted file mode 100644 index 4f3477c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/BedBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/BedBlock.java -+++ b/net/minecraft/world/level/block/BedBlock.java -@@ -346,7 +_,7 @@ - BlockPos blockPos = pos.relative(state.getValue(FACING)); - level.setBlock(blockPos, state.setValue(PART, BedPart.HEAD), 3); - // CraftBukkit start - SPIGOT-7315: Don't updated if we capture block states -- if (level.captureBlockStates) { -+ if (level.getCurrentWorldData().captureBlockStates) { // Folia - region threading - return; - } - // CraftBukkit end diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/Block.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/Block.java.patch deleted file mode 100644 index c72a18a..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/Block.java.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- a/net/minecraft/world/level/block/Block.java -+++ b/net/minecraft/world/level/block/Block.java -@@ -362,8 +_,8 @@ - ItemEntity itemEntity = itemEntitySupplier.get(); - itemEntity.setDefaultPickUpDelay(); - // CraftBukkit start -- if (level.captureDrops != null) { -- level.captureDrops.add(itemEntity); -+ if (level.getCurrentWorldData().captureDrops != null) { // Folia - region threading -+ level.getCurrentWorldData().captureDrops.add(itemEntity); // Folia - region threading - } else { - level.addFreshEntity(itemEntity); - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/BushBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/BushBlock.java.patch deleted file mode 100644 index 64b836c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/BushBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/BushBlock.java -+++ b/net/minecraft/world/level/block/BushBlock.java -@@ -38,7 +_,7 @@ - // CraftBukkit start - if (!state.canSurvive(level, pos)) { - // Suppress during worldgen -- if (!(level instanceof net.minecraft.server.level.ServerLevel serverLevel && serverLevel.hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(serverLevel, pos).isCancelled()) { // Paper -+ if (!(level instanceof net.minecraft.server.level.ServerLevel serverLevel && serverLevel.getCurrentWorldData().hasPhysicsEvent) || !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(serverLevel, pos).isCancelled()) { // Paper // Folia - region threading - return Blocks.AIR.defaultBlockState(); - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch deleted file mode 100644 index da57a25..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/DaylightDetectorBlock.java -+++ b/net/minecraft/world/level/block/DaylightDetectorBlock.java -@@ -110,7 +_,7 @@ - } - - private static void tickEntity(Level level, BlockPos pos, BlockState state, DaylightDetectorBlockEntity blockEntity) { -- if (level.getGameTime() % 20L == 0L) { -+ if (level.getRedstoneGameTime() % 20L == 0L) { // Folia - region threading - updateSignalStrength(state, level, pos); - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch deleted file mode 100644 index e3118c6..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DispenserBlock.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/net/minecraft/world/level/block/DispenserBlock.java -+++ b/net/minecraft/world/level/block/DispenserBlock.java -@@ -50,7 +_,7 @@ - private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior(); - public static final Map DISPENSER_REGISTRY = new IdentityHashMap<>(); - private static final int TRIGGER_DURATION = 4; -- public static boolean eventFired = false; // CraftBukkit -+ public static ThreadLocal eventFired = ThreadLocal.withInitial(() -> Boolean.FALSE); // CraftBukkit // Folia - region threading - - @Override - public MapCodec codec() { -@@ -96,7 +_,7 @@ - DispenseItemBehavior dispenseMethod = this.getDispenseMethod(level, item); - if (dispenseMethod != DispenseItemBehavior.NOOP) { - if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent -- DispenserBlock.eventFired = false; // CraftBukkit - reset event status -+ DispenserBlock.eventFired.set(Boolean.FALSE); // CraftBukkit - reset event status // Folia - region threading - dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item)); - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch deleted file mode 100644 index 43342d5..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/DoublePlantBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/DoublePlantBlock.java -+++ b/net/minecraft/world/level/block/DoublePlantBlock.java -@@ -118,7 +_,7 @@ - - protected static void preventDropFromBottomPart(Level level, BlockPos pos, BlockState state, Player player) { - // CraftBukkit start -- if (((net.minecraft.server.level.ServerLevel)level).hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(level, pos).isCancelled()) { // Paper -+ if (((net.minecraft.server.level.ServerLevel)level).getCurrentWorldData().hasPhysicsEvent && org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(level, pos).isCancelled()) { // Paper // Folia - region threading - return; - } - // CraftBukkit end diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch deleted file mode 100644 index 0d623f8..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/EndGatewayBlock.java.patch +++ /dev/null @@ -1,50 +0,0 @@ ---- a/net/minecraft/world/level/block/EndGatewayBlock.java -+++ b/net/minecraft/world/level/block/EndGatewayBlock.java -@@ -111,16 +_,42 @@ - if (portalPosition == null) { - return null; - } else { -- return entity instanceof ThrownEnderpearl -- ? new TeleportTransition(level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY) // CraftBukkit -- : new TeleportTransition( -- level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY // CraftBukkit -- ); -+ return getTeleportTransition(level, entity, portalPosition); // Folia - region threading - } - } else { - return null; - } - } -+ -+ // Folia start - region threading -+ public static TeleportTransition getTeleportTransition(ServerLevel level, Entity entity, Vec3 portalPosition) { -+ return entity instanceof ThrownEnderpearl -+ ? new TeleportTransition(level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Set.of(), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY) // CraftBukkit -+ : new TeleportTransition( -+ level, portalPosition, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), TeleportTransition.PLACE_PORTAL_TICKET, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_GATEWAY // CraftBukkit -+ ); -+ } -+ -+ @Override -+ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget, BlockPos portalPos) { -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(portalTarget)) { -+ return false; -+ } -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(sourceWorld, portalPos)) { -+ return false; -+ } -+ -+ BlockEntity tile = sourceWorld.getBlockEntity(portalPos); -+ -+ if (!(tile instanceof TheEndGatewayBlockEntity endGateway)) { -+ return false; -+ } -+ -+ return TheEndGatewayBlockEntity.teleportRegionThreading( -+ sourceWorld, portalPos, portalTarget, endGateway, TeleportTransition.PLACE_PORTAL_TICKET -+ ); -+ } -+ // Folia end - region threading - - @Override - protected RenderShape getRenderShape(BlockState state) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch deleted file mode 100644 index 2881691..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/EndPortalBlock.java.patch +++ /dev/null @@ -1,32 +0,0 @@ ---- a/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/net/minecraft/world/level/block/EndPortalBlock.java -@@ -63,7 +_,7 @@ - level.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) return; // Paper - make cancellable - // CraftBukkit end -- if (!level.isClientSide && level.dimension() == Level.END && entity instanceof ServerPlayer serverPlayer && !serverPlayer.seenCredits) { -+ if (false && !level.isClientSide && level.dimension() == Level.END && entity instanceof ServerPlayer serverPlayer && !serverPlayer.seenCredits) { // Folia - region threading - do not show credits - if (level.paperConfig().misc.disableEndCredits) {serverPlayer.seenCredits = true; return;} // Paper - Option to disable end credits - serverPlayer.showEndCredits(); - } else { -@@ -112,6 +_,20 @@ - // CraftBukkit end - } - } -+ -+ // Folia start - region threading -+ @Override -+ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget, BlockPos portalPos) { -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(portalTarget)) { -+ return false; -+ } -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(sourceWorld, portalPos)) { -+ return false; -+ } -+ -+ return portalTarget.endPortalLogicAsync(portalPos); -+ } -+ // Folia end - region threading - - @Override - public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch deleted file mode 100644 index 121cab6..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/FarmBlock.java.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- a/net/minecraft/world/level/block/FarmBlock.java -+++ b/net/minecraft/world/level/block/FarmBlock.java -@@ -95,8 +_,8 @@ - @Override - protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { - int moistureValue = state.getValue(MOISTURE); -- if (moistureValue > 0 && level.paperConfig().tickRates.wetFarmland != 1 && (level.paperConfig().tickRates.wetFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.wetFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks -- if (moistureValue == 0 && level.paperConfig().tickRates.dryFarmland != 1 && (level.paperConfig().tickRates.dryFarmland < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.dryFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks -+ if (moistureValue > 0 && level.paperConfig().tickRates.wetFarmland != 1 && (level.paperConfig().tickRates.wetFarmland < 1 || (level.getRedstoneGameTime() + pos.hashCode()) % level.paperConfig().tickRates.wetFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks // Folia - region threading -+ if (moistureValue == 0 && level.paperConfig().tickRates.dryFarmland != 1 && (level.paperConfig().tickRates.dryFarmland < 1 || (level.getRedstoneGameTime() + pos.hashCode()) % level.paperConfig().tickRates.dryFarmland != 0)) { return; } // Paper - Configurable random tick rates for blocks // Folia - region threading - if (!isNearWater(level, pos) && !level.isRainingAt(pos.above())) { - if (moistureValue > 0) { - org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(level, pos, state.setValue(FarmBlock.MOISTURE, moistureValue - 1), 2); // CraftBukkit diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch deleted file mode 100644 index d30d181..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/FungusBlock.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/level/block/FungusBlock.java -+++ b/net/minecraft/world/level/block/FungusBlock.java -@@ -76,9 +_,9 @@ - // CraftBukkit start - .map((value) -> { - if (this == Blocks.WARPED_FUNGUS) { -- SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; -+ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.WARPED_FUNGUS); // Folia - region threading - } else if (this == Blocks.CRIMSON_FUNGUS) { -- SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; -+ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.CRIMSON_FUNGUS); // Folia - region threading - } - return value; - }) diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch deleted file mode 100644 index 3a4551f..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/HoneyBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/HoneyBlock.java -+++ b/net/minecraft/world/level/block/HoneyBlock.java -@@ -94,7 +_,7 @@ - } - - private void maybeDoSlideAchievement(Entity entity, BlockPos pos) { -- if (entity instanceof ServerPlayer && entity.level().getGameTime() % 20L == 0L) { -+ if (entity instanceof ServerPlayer && entity.level().getRedstoneGameTime() % 20L == 0L) { // Folia - region threading - CriteriaTriggers.HONEY_BLOCK_SLIDE.trigger((ServerPlayer)entity, entity.level().getBlockState(pos)); - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch deleted file mode 100644 index 1401539..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/LightningRodBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/LightningRodBlock.java -+++ b/net/minecraft/world/level/block/LightningRodBlock.java -@@ -116,7 +_,7 @@ - @Override - public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { - if (level.isThundering() -- && level.random.nextInt(200) <= level.getGameTime() % 200L -+ && level.random.nextInt(200) <= level.getRedstoneGameTime() % 200L // Folia - region threading - && pos.getY() == level.getHeight(Heightmap.Types.WORLD_SURFACE, pos.getX(), pos.getZ()) - 1) { - ParticleUtils.spawnParticlesAlongAxis(state.getValue(FACING).getAxis(), level, pos, 0.125, ParticleTypes.ELECTRIC_SPARK, UniformInt.of(1, 2)); - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch deleted file mode 100644 index 0efed8b..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/MushroomBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/MushroomBlock.java -+++ b/net/minecraft/world/level/block/MushroomBlock.java -@@ -94,7 +_,7 @@ - return false; - } else { - level.removeBlock(pos, false); -- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit -+ SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // Folia - region threading - if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { - return true; - } else { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch deleted file mode 100644 index e5a5809..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/NetherPortalBlock.java.patch +++ /dev/null @@ -1,62 +0,0 @@ ---- a/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -181,6 +_,33 @@ - } - } - -+ // Folia start - region threading -+ @Override -+ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget, BlockPos portalPos) { -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(portalTarget)) { -+ return false; -+ } -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(sourceWorld, portalPos)) { -+ return false; -+ } -+ -+ return portalTarget.netherPortalLogicAsync(portalPos); -+ } -+ -+ public static BlockUtil.FoundRectangle findPortalAround(ServerLevel world, BlockPos rough, WorldBorder worldBorder, int searchRadius) { -+ BlockPos found = world.getPortalForcer().findClosestPortalPosition(rough, worldBorder, searchRadius).orElse(null); -+ if (found == null) { -+ return null; -+ } -+ -+ BlockState portalState = world.getBlockStateFromEmptyChunk(found); -+ -+ return BlockUtil.getLargestRectangleAround(found, portalState.getValue(BlockStateProperties.HORIZONTAL_AXIS), 21, Direction.Axis.Y, 21, (pos) -> { -+ return world.getBlockStateFromEmptyChunk(pos) == portalState; -+ }); -+ } -+ // Folia end - region threading -+ - @Nullable - private TeleportTransition getExitPortal(ServerLevel level, Entity entity, BlockPos pos, BlockPos exitPos, boolean isNether, WorldBorder worldBorder, int searchRadius, boolean canCreatePortal, int createRadius) { // CraftBukkit - Optional optional = level.getPortalForcer().findClosestPortalPosition(exitPos, worldBorder, searchRadius); // CraftBukkit -@@ -188,14 +_,14 @@ - TeleportTransition.PostTeleportTransition postTeleportTransition; - if (optional.isPresent()) { - BlockPos blockPos = optional.get(); -- BlockState blockState = level.getBlockState(blockPos); -+ BlockState blockState = level.getBlockStateFromEmptyChunk(blockPos); // Folia - region threading - largestRectangleAround = BlockUtil.getLargestRectangleAround( - blockPos, - blockState.getValue(BlockStateProperties.HORIZONTAL_AXIS), - 21, - Direction.Axis.Y, - 21, -- blockPos1 -> level.getBlockState(blockPos1) == blockState -+ blockPos1 -> level.getBlockStateFromEmptyChunk(blockPos1) == blockState // Folia - region threading - ); - postTeleportTransition = TeleportTransition.PLAY_PORTAL_SOUND.then(entity1 -> entity1.placePortalTicket(blockPos)); - } else if (canCreatePortal) { // CraftBukkit -@@ -238,7 +_,7 @@ - return createDimensionTransition(level, rectangle, axis, relativePortalPosition, entity, postTeleportTransition); - } - -- private static TeleportTransition createDimensionTransition( -+ public static TeleportTransition createDimensionTransition( // Folia - region threading - public - ServerLevel level, - BlockUtil.FoundRectangle rectangle, - Direction.Axis axis, diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/Portal.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/Portal.java.patch deleted file mode 100644 index a64d6b4..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/Portal.java.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- a/net/minecraft/world/level/block/Portal.java -+++ b/net/minecraft/world/level/block/Portal.java -@@ -14,6 +_,10 @@ - @Nullable - TeleportTransition getPortalDestination(ServerLevel level, Entity entity, BlockPos pos); - -+ // Folia start - region threading -+ public boolean portalAsync(ServerLevel sourceWorld, Entity portalTarget, BlockPos portalPos); -+ // Folia end - region threading -+ - default Portal.Transition getLocalTransition() { - return Portal.Transition.NONE; - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/RedStoneWireBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/RedStoneWireBlock.java.patch deleted file mode 100644 index f6bc0b3..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/RedStoneWireBlock.java.patch +++ /dev/null @@ -1,80 +0,0 @@ ---- a/net/minecraft/world/level/block/RedStoneWireBlock.java -+++ b/net/minecraft/world/level/block/RedStoneWireBlock.java -@@ -91,7 +_,7 @@ - private static final float PARTICLE_DENSITY = 0.2F; - private final BlockState crossState; - private final RedstoneWireEvaluator evaluator = new DefaultRedstoneWireEvaluator(this); -- public boolean shouldSignal = true; -+ //public boolean shouldSignal = true; // Folia - region threading - move to regionised world data - - @Override - public MapCodec codec() { -@@ -293,6 +_,11 @@ - // Paper start - Optimize redstone (Eigencraft) - // The bulk of the new functionality is found in RedstoneWireTurbo.java - io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this); -+ // Folia start - region threading -+ private io.papermc.paper.redstone.RedstoneWireTurbo getTurbo(Level world) { -+ return world.getCurrentWorldData().turbo; -+ } -+ // Folia end - region threading - - /* - * Modified version of pre-existing updateSurroundingRedstone, which is called from -@@ -308,7 +_,7 @@ - if (orientation != null) { - source = pos.relative(orientation.getFront().getOpposite()); - } -- turbo.updateSurroundingRedstone(worldIn, pos, state, source); -+ getTurbo(worldIn).updateSurroundingRedstone(worldIn, pos, state, source); // Folia - region threading - return; - } - updatePowerStrength(worldIn, pos, state, orientation, blockAdded); -@@ -336,7 +_,7 @@ - // [Space Walker] suppress shape updates and emit those manually to - // bypass the new neighbor update stack. - if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) { -- turbo.updateNeighborShapes(level, pos, state); -+ this.getTurbo(level).updateNeighborShapes(level, pos, state); // Folia - region threading - } - } - } -@@ -353,9 +_,9 @@ - } - - public int getBlockSignal(Level level, BlockPos pos) { -- this.shouldSignal = false; -+ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal = false; // Folia - region threading - int bestNeighborSignal = level.getBestNeighborSignal(pos); -- this.shouldSignal = true; -+ io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal = true; // Folia - region threading - return bestNeighborSignal; - } - -@@ -450,12 +_,12 @@ - - @Override - protected int getDirectSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) { -- return !this.shouldSignal ? 0 : blockState.getSignal(blockAccess, pos, side); -+ return !io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal ? 0 : blockState.getSignal(blockAccess, pos, side); // Folia - region threading - } - - @Override - protected int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) { -- if (this.shouldSignal && side != Direction.DOWN) { -+ if (io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().shouldSignal && side != Direction.DOWN) { // Folia - region threading - int powerValue = blockState.getValue(POWER); - if (powerValue == 0) { - return 0; -@@ -487,7 +_,10 @@ - - @Override - protected boolean isSignalSource(BlockState state) { -- return this.shouldSignal; -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); -+ return worldData == null || worldData.shouldSignal; -+ // Folia end - region threading - } - - public static int getColorForPower(int power) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch deleted file mode 100644 index 5a3c83c..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch +++ /dev/null @@ -1,53 +0,0 @@ ---- a/net/minecraft/world/level/block/RedstoneTorchBlock.java -+++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java -@@ -73,10 +_,10 @@ - protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { - boolean hasNeighborSignal = this.hasNeighborSignal(level, pos, state); - // Paper start - Faster redstone torch rapid clock removal -- java.util.ArrayDeque redstoneUpdateInfos = level.redstoneUpdateInfos; -+ java.util.ArrayDeque redstoneUpdateInfos = level.getCurrentWorldData().redstoneUpdateInfos; // Folia - region threading - if (redstoneUpdateInfos != null) { - RedstoneTorchBlock.Toggle curr; -- while ((curr = redstoneUpdateInfos.peek()) != null && level.getGameTime() - curr.when > 60L) { -+ while ((curr = redstoneUpdateInfos.peek()) != null && level.getRedstoneGameTime() - curr.when > 60L) { // Folia - region threading - redstoneUpdateInfos.poll(); - } - } -@@ -154,13 +_,13 @@ - - private static boolean isToggledTooFrequently(Level level, BlockPos pos, boolean logToggle) { - // Paper start - Faster redstone torch rapid clock removal -- java.util.ArrayDeque list = level.redstoneUpdateInfos; -+ java.util.ArrayDeque list = level.getCurrentWorldData().redstoneUpdateInfos; // Folia - region threading - if (list == null) { -- list = level.redstoneUpdateInfos = new java.util.ArrayDeque<>(); -+ list = level.getCurrentWorldData().redstoneUpdateInfos = new java.util.ArrayDeque<>(); // Folia - region threading - } - // Paper end - Faster redstone torch rapid clock removal - if (logToggle) { -- list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), level.getGameTime())); -+ list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), level.getRedstoneGameTime())); // Folia - region threading - } - - int i = 0; -@@ -182,12 +_,18 @@ - } - - public static class Toggle { -- final BlockPos pos; -- final long when; -+ public final BlockPos pos; // Folia - region threading -+ long when; // Folia - region threading - - public Toggle(BlockPos pos, long when) { - this.pos = pos; - this.when = when; - } -+ -+ // Folia start - region ticking -+ public void offsetTime(long offset) { -+ this.when += offset; -+ } -+ // Folia end - region ticking - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch deleted file mode 100644 index 8a11256..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch +++ /dev/null @@ -1,39 +0,0 @@ ---- a/net/minecraft/world/level/block/SaplingBlock.java -+++ b/net/minecraft/world/level/block/SaplingBlock.java -@@ -26,7 +_,7 @@ - protected static final float AABB_OFFSET = 6.0F; - protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 12.0, 14.0); - protected final TreeGrower treeGrower; -- public static org.bukkit.TreeType treeType; // CraftBukkit -+ public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // CraftBukkit // Folia - region threading - - @Override - public MapCodec codec() { -@@ -56,18 +_,19 @@ - level.setBlock(pos, state.cycle(STAGE), 4); - } else { - // CraftBukkit start -- if (level.captureTreeGeneration) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading -+ if (worldData.captureTreeGeneration) { // Folia - region threading - this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); - } else { -- level.captureTreeGeneration = true; -+ worldData.captureTreeGeneration = true; // Folia - region threading - this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); -- level.captureTreeGeneration = false; -- if (!level.capturedBlockStates.isEmpty()) { -- org.bukkit.TreeType treeType = SaplingBlock.treeType; -- SaplingBlock.treeType = null; -+ worldData.captureTreeGeneration = false; // Folia - region threading -+ if (!worldData.capturedBlockStates.isEmpty()) { // Folia - region threading -+ org.bukkit.TreeType treeType = SaplingBlock.treeTypeRT.get(); // Folia - region threading -+ SaplingBlock.treeTypeRT.set(null); // Folia - region threading - org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld()); -- java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); -- level.capturedBlockStates.clear(); -+ java.util.List blocks = new java.util.ArrayList<>(worldData.capturedBlockStates.values()); // Folia - region threading -+ worldData.capturedBlockStates.clear(); // Folia - region threading - org.bukkit.event.world.StructureGrowEvent event = null; - if (treeType != null) { - event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch deleted file mode 100644 index d0537b4..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java -+++ b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java -@@ -50,7 +_,7 @@ - - @Override - protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { -- if (this instanceof GrassBlock && level.paperConfig().tickRates.grassSpread != 1 && (level.paperConfig().tickRates.grassSpread < 1 || (net.minecraft.server.MinecraftServer.currentTick + pos.hashCode()) % level.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks -+ if (this instanceof GrassBlock && level.paperConfig().tickRates.grassSpread != 1 && (level.paperConfig().tickRates.grassSpread < 1 || (io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + pos.hashCode()) % level.paperConfig().tickRates.grassSpread != 0)) { return; } // Paper - Configurable random tick rates for blocks // Folia - regionised ticking - // Paper start - Perf: optimize dirt and snow spreading - final net.minecraft.world.level.chunk.ChunkAccess cachedBlockChunk = level.getChunkIfLoaded(pos); - if (cachedBlockChunk == null) { // Is this needed? diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch deleted file mode 100644 index e953dea..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/WitherSkullBlock.java -+++ b/net/minecraft/world/level/block/WitherSkullBlock.java -@@ -51,7 +_,7 @@ - } - - public static void checkSpawn(Level level, BlockPos pos, SkullBlockEntity blockEntity) { -- if (level.captureBlockStates) return; // CraftBukkit -+ if (level.getCurrentWorldData().captureBlockStates) return; // CraftBukkit // Folia - region threading - if (!level.isClientSide) { - BlockState blockState = blockEntity.getBlockState(); - boolean flag = blockState.is(Blocks.WITHER_SKELETON_SKULL) || blockState.is(Blocks.WITHER_SKELETON_WALL_SKULL); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch deleted file mode 100644 index bad97de..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -211,7 +_,7 @@ - } - - int i = blockEntity.levels; final int originalLevels = i; // Paper - OBFHELPER -- if (level.getGameTime() % 80L == 0L) { -+ if (level.getRedstoneGameTime() % 80L == 0L) { // Folia - region threading - if (!blockEntity.beamSections.isEmpty()) { - blockEntity.levels = updateBase(level, x, y, z); - } -@@ -345,7 +_,7 @@ - list = level.getEntitiesOfClass(Player.class, aabb); // Diff from applyEffect - } else { - list = new java.util.ArrayList<>(); -- for (final Player player : level.players()) { -+ for (final Player player : level.getLocalPlayers()) { // Folia - region threading - if (!net.minecraft.world.entity.EntitySelector.NO_SPECTATORS.test(player)) continue; - if (player.getBoundingBox().intersects(aabb)) { - list.add(player); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch deleted file mode 100644 index 370e8e9..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/BlockEntity.java.patch +++ /dev/null @@ -1,33 +0,0 @@ ---- a/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -26,7 +_,7 @@ - import org.slf4j.Logger; - - public abstract class BlockEntity { -- static boolean ignoreBlockEntityUpdates; // Paper - Perf: Optimize Hoppers -+ static final ThreadLocal IGNORE_TILE_UPDATES = ThreadLocal.withInitial(() -> Boolean.FALSE); // Paper - Perf: Optimize Hoppers // Folia - region threading - // CraftBukkit start - data containers - private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); - public org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer; -@@ -40,6 +_,12 @@ - private BlockState blockState; - private DataComponentMap components = DataComponentMap.EMPTY; - -+ // Folia start - region ticking -+ public void updateTicks(final long fromTickOffset, final long fromRedstoneTimeOffset) { -+ -+ } -+ // Folia end - region ticking -+ - public BlockEntity(BlockEntityType type, BlockPos pos, BlockState blockState) { - this.type = type; - this.worldPosition = pos.immutable(); -@@ -197,7 +_,7 @@ - - public void setChanged() { - if (this.level != null) { -- if (ignoreBlockEntityUpdates) return; // Paper - Perf: Optimize Hoppers -+ if (IGNORE_TILE_UPDATES.get().booleanValue()) return; // Paper - Perf: Optimize Hoppers // Folia - region threading - setChanged(this.level, this.worldPosition, this.blockState); - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch deleted file mode 100644 index 546e29e..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch +++ /dev/null @@ -1,16 +0,0 @@ ---- a/net/minecraft/world/level/block/entity/CommandBlockEntity.java -+++ b/net/minecraft/world/level/block/entity/CommandBlockEntity.java -@@ -66,6 +_,13 @@ - ); - } - -+ // Folia start -+ @Override -+ public void threadCheck() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) CommandBlockEntity.this.level, CommandBlockEntity.this.worldPosition, "Asynchronous sendSystemMessage to a command block"); -+ } -+ // Folia end -+ - @Override - public boolean isValid() { - return !CommandBlockEntity.this.isRemoved(); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch deleted file mode 100644 index 66ccbdd..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -+++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -@@ -81,7 +_,7 @@ - - public static void clientTick(Level level, BlockPos pos, BlockState state, ConduitBlockEntity blockEntity) { - blockEntity.tickCount++; -- long gameTime = level.getGameTime(); -+ long gameTime = level.getRedstoneGameTime(); // Folia - region threading - List list = blockEntity.effectBlocks; - if (gameTime % 40L == 0L) { - blockEntity.isActive = updateShape(level, pos, list); -@@ -97,7 +_,7 @@ - - public static void serverTick(Level level, BlockPos pos, BlockState state, ConduitBlockEntity blockEntity) { - blockEntity.tickCount++; -- long gameTime = level.getGameTime(); -+ long gameTime = level.getRedstoneGameTime(); // Folia - region threading - List list = blockEntity.effectBlocks; - if (gameTime % 40L == 0L) { - boolean flag = updateShape(level, pos, list); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch deleted file mode 100644 index 1786d77..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch +++ /dev/null @@ -1,150 +0,0 @@ ---- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java -+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java -@@ -34,7 +_,7 @@ - private static final int[][] CACHED_SLOTS = new int[54][]; - private NonNullList items = NonNullList.withSize(5, ItemStack.EMPTY); - public int cooldownTime = -1; -- private long tickedGameTime; -+ private long tickedGameTime = Long.MIN_VALUE; // Folia - region threading - private Direction facing; - - // CraftBukkit start - add fields and methods -@@ -67,6 +_,15 @@ - } - // CraftBukkit end - -+ // Folia start - region threading -+ @Override -+ public void updateTicks(final long fromTickOffset, final long fromRedstoneTimeOffset) { -+ super.updateTicks(fromTickOffset, fromRedstoneTimeOffset); -+ if (this.tickedGameTime != Long.MIN_VALUE) { -+ this.tickedGameTime += fromRedstoneTimeOffset; -+ } -+ } -+ // Folia end - region threading - - public HopperBlockEntity(BlockPos pos, BlockState blockState) { - super(BlockEntityType.HOPPER, pos, blockState); -@@ -125,7 +_,7 @@ - - public static void pushItemsTick(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity) { - blockEntity.cooldownTime--; -- blockEntity.tickedGameTime = level.getGameTime(); -+ blockEntity.tickedGameTime = level.getRedstoneGameTime(); // Folia - region threading - if (!blockEntity.isOnCooldown()) { - blockEntity.setCooldown(0); - // Spigot start -@@ -213,12 +_,11 @@ - } - - // Paper start - Perf: Optimize Hoppers -- public static boolean skipHopperEvents; -- private static boolean skipPullModeEventFire; -- private static boolean skipPushModeEventFire; -+ // Folia - region threading - moved to RegionizedWorldData - - private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { -- skipPushModeEventFire = skipHopperEvents; -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading -+ worldData.skipPushModeEventFire = worldData.skipHopperEvents; // Folia - region threading - boolean foundItem = false; - for (int i = 0; i < hopper.getContainerSize(); ++i) { - final ItemStack item = hopper.getItem(i); -@@ -233,7 +_,7 @@ - - // We only need to fire the event once to give protection plugins a chance to cancel this event - // Because nothing uses getItem, every event call should end up the same result. -- if (!skipPushModeEventFire) { -+ if (!worldData.skipPushModeEventFire) { // Folia - region threading - movedItem = callPushMoveEvent(destination, movedItem, hopper); - if (movedItem == null) { // cancelled - origItemStack.setCount(originalItemCount); -@@ -263,13 +_,14 @@ - } - - private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading - ItemStack movedItem = origItemStack; - final int originalItemCount = origItemStack.getCount(); - final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); - container.setChanged(); // original logic always marks source inv as changed even if no move happens. - movedItem.setCount(movedItemCount); - -- if (!skipPullModeEventFire) { -+ if (!worldData.skipPullModeEventFire) { // Folia - region threading - movedItem = callPullMoveEvent(hopper, container, movedItem); - if (movedItem == null) { // cancelled - origItemStack.setCount(originalItemCount); -@@ -289,9 +_,9 @@ - origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); - } - -- ignoreBlockEntityUpdates = true; -+ IGNORE_TILE_UPDATES.set(true); // Folia - region threading - container.setItem(i, origItemStack); -- ignoreBlockEntityUpdates = false; -+ IGNORE_TILE_UPDATES.set(false); // Folia - region threading - container.setChanged(); - return true; - } -@@ -306,6 +_,7 @@ - - @Nullable - private static ItemStack callPushMoveEvent(Container destination, ItemStack itemStack, HopperBlockEntity hopper) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading - final org.bukkit.inventory.Inventory destinationInventory = getInventory(destination); - final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent( - hopper.getOwner(false).getInventory(), -@@ -315,7 +_,7 @@ - ); - final boolean result = event.callEvent(); - if (!event.calledGetItem && !event.calledSetItem) { -- skipPushModeEventFire = true; -+ worldData.skipPushModeEventFire = true; // Folia - region threading - } - if (!result) { - applyCooldown(hopper); -@@ -331,6 +_,7 @@ - - @Nullable - private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading - final org.bukkit.inventory.Inventory sourceInventory = getInventory(container); - final org.bukkit.inventory.Inventory destination = getInventory(hopper); - -@@ -338,7 +_,7 @@ - final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), destination, false); - final boolean result = event.callEvent(); - if (!event.calledGetItem && !event.calledSetItem) { -- skipPullModeEventFire = true; -+ worldData.skipPullModeEventFire = true; // Folia - region threading - } - if (!result) { - applyCooldown(hopper); -@@ -524,12 +_,13 @@ - } - - public static boolean suckInItems(Level level, Hopper hopper) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading - BlockPos blockPos = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ()); - BlockState blockState = level.getBlockState(blockPos); - Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState); - if (sourceContainer != null) { - Direction direction = Direction.DOWN; -- skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers -+ worldData.skipPullModeEventFire = worldData.skipHopperEvents; // Paper - Perf: Optimize Hoppers // Folia - region threading - - for (int i : getSlots(sourceContainer, direction)) { - if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot -@@ -678,9 +_,9 @@ - stack = stack.split(destination.getMaxStackSize()); - } - // Spigot end -- ignoreBlockEntityUpdates = true; // Paper - Perf: Optimize Hoppers -+ IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading - destination.setItem(slot, stack); -- ignoreBlockEntityUpdates = false; // Paper - Perf: Optimize Hoppers -+ IGNORE_TILE_UPDATES.set(Boolean.FALSE); // Paper - Perf: Optimize Hoppers // Folia - region threading - stack = leftover; // Paper - Make hoppers respect inventory max stack size - flag = true; - } else if (canMergeItems(item, stack)) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch deleted file mode 100644 index f008c88..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -+++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -@@ -43,9 +_,9 @@ - // Paper end - Fix NPE in SculkBloomEvent world access - - public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) { -- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. -+ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(sculkCatalyst.getBlockPos()); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // Folia - region threading - sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true); -- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit -+ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(null); // CraftBukkit // Folia - region threading - } - - @Override diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch deleted file mode 100644 index 64a6402..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch +++ /dev/null @@ -1,246 +0,0 @@ ---- a/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java -+++ b/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java -@@ -35,9 +_,12 @@ - public long age; - private int teleportCooldown; - @Nullable -- public BlockPos exitPortal; -+ public volatile BlockPos exitPortal; // Folia - region threading - volatile - public boolean exactTeleport; - -+ private static final java.util.concurrent.atomic.AtomicLong SEARCHING_FOR_EXIT_ID_GENERATOR = new java.util.concurrent.atomic.AtomicLong(); // Folia - region threading -+ private Long searchingForExitId; // Folia - region threading -+ - public TheEndGatewayBlockEntity(BlockPos pos, BlockState blockState) { - super(BlockEntityType.END_GATEWAY, pos, blockState); - } -@@ -129,6 +_,104 @@ - } - } - -+ // Folia start - region threading -+ private void trySearchForExit(ServerLevel world, BlockPos fromPos) { -+ if (this.searchingForExitId != null) { -+ return; -+ } -+ this.searchingForExitId = Long.valueOf(SEARCHING_FOR_EXIT_ID_GENERATOR.getAndIncrement()); -+ int chunkX = fromPos.getX() >> 4; -+ int chunkZ = fromPos.getZ() >> 4; -+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.addTicketAtLevel( -+ net.minecraft.server.level.TicketType.END_GATEWAY_EXIT_SEARCH, -+ chunkX, chunkZ, -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.BLOCK_TICKING_TICKET_LEVEL, -+ this.searchingForExitId -+ ); -+ -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable complete = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); -+ -+ complete.addWaiter((tpLoc, throwable) -> { -+ // create the exit portal -+ TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", tpLoc); -+ TheEndGatewayBlockEntity.spawnGatewayPortal(world, tpLoc, EndGatewayConfiguration.knownExit(fromPos, false)); -+ -+ // need to go onto the tick thread to avoid saving issues -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( -+ world, chunkX, chunkZ, -+ () -> { -+ // update the exit portal location -+ TheEndGatewayBlockEntity.this.exitPortal = tpLoc; -+ -+ // remove ticket keeping the gateway loaded -+ world.moonrise$getChunkTaskScheduler().chunkHolderManager.removeTicketAtLevel( -+ net.minecraft.server.level.TicketType.END_GATEWAY_EXIT_SEARCH, -+ chunkX, chunkZ, -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.BLOCK_TICKING_TICKET_LEVEL, -+ this.searchingForExitId -+ ); -+ TheEndGatewayBlockEntity.this.searchingForExitId = null; -+ } -+ ); -+ }); -+ -+ findOrCreateValidTeleportPosRegionThreading(world, fromPos, complete); -+ } -+ -+ public static boolean teleportRegionThreading(ServerLevel portalWorld, BlockPos portalPos, -+ net.minecraft.world.entity.Entity toTeleport, -+ TheEndGatewayBlockEntity portalTile, -+ net.minecraft.world.level.portal.TeleportTransition.PostTeleportTransition post) { -+ // can we even teleport in this dimension? -+ if (portalTile.exitPortal == null && portalWorld.getTypeKey() != net.minecraft.world.level.dimension.LevelStem.END) { -+ return false; -+ } -+ -+ // First, find the position we are trying to teleport to -+ BlockPos teleportPos = portalTile.exitPortal; -+ boolean isExactTeleport = portalTile.exactTeleport; -+ -+ if (teleportPos == null) { -+ portalTile.trySearchForExit(portalWorld, portalPos); -+ return false; -+ } -+ -+ // note: we handle the position from the TeleportTransition -+ net.minecraft.world.level.portal.TeleportTransition teleport = net.minecraft.world.level.block.EndGatewayBlock.getTeleportTransition( -+ portalWorld, toTeleport, Vec3.atCenterOf(teleportPos) -+ ); -+ -+ -+ if (isExactTeleport) { -+ // blind teleport -+ return toTeleport.teleportAsync( -+ teleport, net.minecraft.world.entity.Entity.TELEPORT_FLAG_LOAD_CHUNK | net.minecraft.world.entity.Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS, -+ post == null ? null : (net.minecraft.world.entity.Entity teleportedEntity) -> { -+ post.onTransition(teleportedEntity); -+ } -+ ); -+ } else { -+ // we could hack around by first loading the chunks, then calling back to here and checking if the entity -+ // should be teleported, something something else... -+ // however, we know the target location cannot differ by one region section: so we can -+ // just teleport and adjust the position after -+ return toTeleport.teleportAsync( -+ teleport, net.minecraft.world.entity.Entity.TELEPORT_FLAG_LOAD_CHUNK | net.minecraft.world.entity.Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS, -+ (net.minecraft.world.entity.Entity teleportedEntity) -> { -+ // adjust to the final exit position -+ Vec3 adjusted = Vec3.atCenterOf(TheEndGatewayBlockEntity.findExitPosition(portalWorld, teleportPos)); -+ // teleportTo will adjust rider positions -+ teleportedEntity.teleportTo(adjusted.x, adjusted.y, adjusted.z); -+ -+ if (post != null) { -+ post.onTransition(teleportedEntity); -+ } -+ } -+ ); -+ } -+ } -+ // Folia end - region threading -+ - @Nullable - public Vec3 getPortalPosition(ServerLevel level, BlockPos pos) { - if (this.exitPortal == null && level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) { // CraftBukkit - work in alternate worlds -@@ -173,6 +_,124 @@ - - return findTallestBlock(level, blockPos, 16, true); - } -+ -+ // Folia start - region threading -+ private static void findOrCreateValidTeleportPosRegionThreading(ServerLevel world, BlockPos pos, -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable complete) { -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable tentativeSelection = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>(); -+ -+ tentativeSelection.addWaiter((vec3d, throwable) -> { -+ LevelChunk chunk = TheEndGatewayBlockEntity.getChunk(world, vec3d); -+ BlockPos blockposition1 = TheEndGatewayBlockEntity.findValidSpawnInChunk(chunk); -+ if (blockposition1 == null) { -+ BlockPos blockposition2 = BlockPos.containing(vec3d.x + 0.5D, 75.0D, vec3d.z + 0.5D); -+ -+ TheEndGatewayBlockEntity.LOGGER.debug("Failed to find a suitable block to teleport to, spawning an island on {}", blockposition2); -+ world.registryAccess().lookup(Registries.CONFIGURED_FEATURE).flatMap((iregistry) -> { -+ return iregistry.get(EndFeatures.END_ISLAND); -+ }).ifPresent((holder_c) -> { -+ ((net.minecraft.world.level.levelgen.feature.ConfiguredFeature) holder_c.value()).place(world, world.getChunkSource().getGenerator(), RandomSource.create(blockposition2.asLong()), blockposition2); -+ }); -+ blockposition1 = blockposition2; -+ } else { -+ TheEndGatewayBlockEntity.LOGGER.debug("Found suitable block to teleport to: {}", blockposition1); -+ } -+ -+ // Here, there is no guarantee the chunks in 1 radius are in this region due to the fact that we just chained -+ // possibly 16x chunk loads along an axis (findExitPortalXZPosTentativeRegionThreading) using the chunk queue -+ // (regioniser only guarantees at least 8 chunks along a single axis) -+ // so, we need to schedule for the next tick -+ int posX = blockposition1.getX(); -+ int posZ = blockposition1.getZ(); -+ int radius = 16; -+ -+ BlockPos finalBlockPosition1 = blockposition1; -+ world.moonrise$loadChunksAsync(blockposition1, radius, -+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL, -+ (java.util.List chunks) -> { -+ // make sure chunks are kept loaded -+ for (net.minecraft.world.level.chunk.ChunkAccess access : chunks) { -+ world.chunkSource.addTicketAtLevel( -+ net.minecraft.server.level.TicketType.DELAYED, access.getPos(), -+ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.FULL_LOADED_TICKET_LEVEL, -+ net.minecraft.util.Unit.INSTANCE -+ ); -+ } -+ // now after the chunks are loaded, we can delay by one tick -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( -+ world, posX >> 4, posZ >> 4, () -> { -+ // find final location -+ BlockPos tpLoc = TheEndGatewayBlockEntity.findTallestBlock(world, finalBlockPosition1, radius, true).above(GATEWAY_HEIGHT_ABOVE_SURFACE); -+ -+ // done -+ complete.complete(tpLoc); -+ } -+ ); -+ } -+ ); -+ }); -+ -+ // fire off chain -+ findExitPortalXZPosTentativeRegionThreading(world, pos, tentativeSelection); -+ } -+ -+ private static void findExitPortalXZPosTentativeRegionThreading(ServerLevel world, BlockPos pos, -+ ca.spottedleaf.concurrentutil.completable.CallbackCompletable complete) { -+ Vec3 posDirFromOrigin = new Vec3(pos.getX(), 0.0D, pos.getZ()).normalize(); -+ Vec3 posDirExtruded = posDirFromOrigin.scale(1024.0D); -+ -+ class Vars { -+ int i = 16; -+ boolean mode = false; -+ Vec3 currPos = posDirExtruded; -+ } -+ Vars vars = new Vars(); -+ -+ Runnable handle = new Runnable() { -+ @Override -+ public void run() { -+ if (vars.mode != TheEndGatewayBlockEntity.isChunkEmpty(world, vars.currPos)) { -+ vars.i = 0; // fall back to completing -+ } -+ -+ // try to load next chunk -+ if (vars.i-- <= 0) { -+ if (vars.mode) { -+ complete.complete(vars.currPos); -+ return; -+ } -+ vars.mode = true; -+ vars.i = 16; -+ } -+ -+ vars.currPos = vars.currPos.add(posDirFromOrigin.scale(vars.mode ? 16.0 : -16.0)); -+ // schedule next iteration -+ world.moonrise$getChunkTaskScheduler().scheduleChunkLoad( -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(vars.currPos), -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(vars.currPos), -+ net.minecraft.world.level.chunk.status.ChunkStatus.FULL, -+ true, -+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL, -+ (chunk) -> { -+ this.run(); -+ } -+ ); -+ } -+ }; -+ -+ // kick off first chunk load -+ world.moonrise$getChunkTaskScheduler().scheduleChunkLoad( -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(posDirExtruded), -+ ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(posDirExtruded), -+ net.minecraft.world.level.chunk.status.ChunkStatus.FULL, -+ true, -+ ca.spottedleaf.concurrentutil.util.Priority.NORMAL, -+ (chunk) -> { -+ handle.run(); -+ } -+ ); -+ } -+ // Folia end - region threading - - private static Vec3 findExitPortalXZPosTentative(ServerLevel level, BlockPos pos) { - Vec3 vec3 = new Vec3(pos.getX(), 0.0, pos.getZ()).normalize(); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/TickingBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/TickingBlockEntity.java.patch deleted file mode 100644 index 47b43a6..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/TickingBlockEntity.java.patch +++ /dev/null @@ -1,9 +0,0 @@ ---- a/net/minecraft/world/level/block/entity/TickingBlockEntity.java -+++ b/net/minecraft/world/level/block/entity/TickingBlockEntity.java -@@ -10,4 +_,6 @@ - BlockPos getPos(); - - String getType(); -+ -+ BlockEntity getTileEntity(); // Folia - region threading - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch deleted file mode 100644 index fccd549..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/grower/TreeGrower.java.patch +++ /dev/null @@ -1,84 +0,0 @@ ---- a/net/minecraft/world/level/block/grower/TreeGrower.java -+++ b/net/minecraft/world/level/block/grower/TreeGrower.java -@@ -203,56 +_,58 @@ - - // CraftBukkit start - private void setTreeType(Holder> holder) { -+ org.bukkit.TreeType treeType; // Folia - region threading - ResourceKey> treeFeature = holder.unwrapKey().get(); - if (treeFeature == TreeFeatures.OAK || treeFeature == TreeFeatures.OAK_BEES_005) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; -+ treeType = org.bukkit.TreeType.TREE; // Folia - region threading - } else if (treeFeature == TreeFeatures.HUGE_RED_MUSHROOM) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM; -+ treeType = org.bukkit.TreeType.RED_MUSHROOM; // Folia - region threading - } else if (treeFeature == TreeFeatures.HUGE_BROWN_MUSHROOM) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM; -+ treeType = org.bukkit.TreeType.BROWN_MUSHROOM; // Folia - region threading - } else if (treeFeature == TreeFeatures.JUNGLE_TREE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE; -+ treeType = org.bukkit.TreeType.COCOA_TREE; // Folia - region threading - } else if (treeFeature == TreeFeatures.JUNGLE_TREE_NO_VINE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE; -+ treeType = org.bukkit.TreeType.SMALL_JUNGLE; // Folia - region threading - } else if (treeFeature == TreeFeatures.PINE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD; -+ treeType = org.bukkit.TreeType.TALL_REDWOOD; // Folia - region threading - } else if (treeFeature == TreeFeatures.SPRUCE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD; -+ treeType = org.bukkit.TreeType.REDWOOD; // Folia - region threading - } else if (treeFeature == TreeFeatures.ACACIA) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA; -+ treeType = org.bukkit.TreeType.ACACIA; // Folia - region threading - } else if (treeFeature == TreeFeatures.BIRCH || treeFeature == TreeFeatures.BIRCH_BEES_005) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH; -+ treeType = org.bukkit.TreeType.BIRCH; // Folia - region threading - } else if (treeFeature == TreeFeatures.SUPER_BIRCH_BEES_0002) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH; -+ treeType = org.bukkit.TreeType.TALL_BIRCH; // Folia - region threading - } else if (treeFeature == TreeFeatures.SWAMP_OAK) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP; -+ treeType = org.bukkit.TreeType.SWAMP; // Folia - region threading - } else if (treeFeature == TreeFeatures.FANCY_OAK || treeFeature == TreeFeatures.FANCY_OAK_BEES_005) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE; -+ treeType = org.bukkit.TreeType.BIG_TREE; // Folia - region threading - } else if (treeFeature == TreeFeatures.JUNGLE_BUSH) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH; -+ treeType = org.bukkit.TreeType.JUNGLE_BUSH; // Folia - region threading - } else if (treeFeature == TreeFeatures.DARK_OAK) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK; -+ treeType = org.bukkit.TreeType.DARK_OAK; // Folia - region threading - } else if (treeFeature == TreeFeatures.MEGA_SPRUCE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD; -+ treeType = org.bukkit.TreeType.MEGA_REDWOOD; // Folia - region threading - } else if (treeFeature == TreeFeatures.MEGA_PINE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE; -+ treeType = org.bukkit.TreeType.MEGA_PINE; // Folia - region threading - } else if (treeFeature == TreeFeatures.MEGA_JUNGLE_TREE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE; -+ treeType = org.bukkit.TreeType.JUNGLE; // Folia - region threading - } else if (treeFeature == TreeFeatures.AZALEA_TREE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA; -+ treeType = org.bukkit.TreeType.AZALEA; // Folia - region threading - } else if (treeFeature == TreeFeatures.MANGROVE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE; -+ treeType = org.bukkit.TreeType.MANGROVE; // Folia - region threading - } else if (treeFeature == TreeFeatures.TALL_MANGROVE) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE; -+ treeType = org.bukkit.TreeType.TALL_MANGROVE; // Folia - region threading - } else if (treeFeature == TreeFeatures.CHERRY || treeFeature == TreeFeatures.CHERRY_BEES_005) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY; -+ treeType = org.bukkit.TreeType.CHERRY; // Folia - region threading - } else if (treeFeature == TreeFeatures.PALE_OAK || treeFeature == TreeFeatures.PALE_OAK_BONEMEAL) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK; -+ treeType = org.bukkit.TreeType.PALE_OAK; // Folia - region threading - } else if (treeFeature == TreeFeatures.PALE_OAK_CREAKING) { -- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; -+ treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; // Folia - region threading - } else { - throw new IllegalArgumentException("Unknown tree generator " + treeFeature); - } -+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(treeType); // Folia - region threading - } - // CraftBukkit end - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch deleted file mode 100644 index 2810e1b..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -139,7 +_,7 @@ - && pistonMovingBlockEntity.isExtending() - && ( - pistonMovingBlockEntity.getProgress(0.0F) < 0.5F -- || level.getGameTime() == pistonMovingBlockEntity.getLastTicked() -+ || level.getRedstoneGameTime() == pistonMovingBlockEntity.getLastTicked() // Folia - region threading - || ((ServerLevel)level).isHandlingTick() - )) { - i = 2; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch deleted file mode 100644 index cf6a175..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java.patch +++ /dev/null @@ -1,43 +0,0 @@ ---- a/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -+++ b/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -@@ -41,9 +_,19 @@ - private static final ThreadLocal NOCLIP = ThreadLocal.withInitial(() -> null); - private float progress; - private float progressO; -- private long lastTicked; -+ private long lastTicked = Long.MIN_VALUE; // Folia - region threading - private int deathTicks; - -+ // Folia start - region threading -+ @Override -+ public void updateTicks(long fromTickOffset, long fromRedstoneTimeOffset) { -+ super.updateTicks(fromTickOffset, fromRedstoneTimeOffset); -+ if (this.lastTicked != Long.MIN_VALUE) { -+ this.lastTicked += fromRedstoneTimeOffset; -+ } -+ } -+ // Folia end - region threading -+ - public PistonMovingBlockEntity(BlockPos pos, BlockState blockState) { - super(BlockEntityType.PISTON, pos, blockState); - } -@@ -150,8 +_,8 @@ - - entity.setDeltaMovement(d1, d2, d3); - // Paper - EAR items stuck in slime pushed by a piston -- entity.activatedTick = Math.max(entity.activatedTick, net.minecraft.server.MinecraftServer.currentTick + 10); -- entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 10); -+ entity.activatedTick = Math.max(entity.activatedTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 10); // Folia - region threading -+ entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick() + 10); // Folia - region threading - // Paper end - break; - } -@@ -292,7 +_,7 @@ - } - - public static void tick(Level level, BlockPos pos, BlockState state, PistonMovingBlockEntity blockEntity) { -- blockEntity.lastTicked = level.getGameTime(); -+ blockEntity.lastTicked = level.getRedstoneGameTime(); // Folia - region threading - blockEntity.progressO = blockEntity.progress; - if (blockEntity.progressO >= 1.0F) { - if (level.isClientSide && blockEntity.deathTicks < 5) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch deleted file mode 100644 index 8c37a6f..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/border/WorldBorder.java.patch +++ /dev/null @@ -1,31 +0,0 @@ ---- a/net/minecraft/world/level/border/WorldBorder.java -+++ b/net/minecraft/world/level/border/WorldBorder.java -@@ -30,6 +_,8 @@ - public static final WorldBorder.Settings DEFAULT_SETTINGS = new WorldBorder.Settings(0.0, 0.0, 0.2, 5.0, 5, 15, 5.999997E7F, 0L, 0.0); - public net.minecraft.server.level.ServerLevel world; // CraftBukkit - -+ // Folia - region threading - TODO make this shit thread-safe -+ - public boolean isWithinBounds(BlockPos pos) { - return this.isWithinBounds(pos.getX(), pos.getZ()); - } -@@ -43,16 +_,14 @@ - } - - // Paper start - Bound treasure maps to world border -- private final BlockPos.MutableBlockPos mutPos = new BlockPos.MutableBlockPos(); -+ private static final ThreadLocal mutPos = ThreadLocal.withInitial(() -> new BlockPos.MutableBlockPos()); // Folia - region threading - - public boolean isBlockInBounds(int chunkX, int chunkZ) { -- this.mutPos.set(chunkX, 64, chunkZ); -- return this.isWithinBounds(this.mutPos); -+ return this.isWithinBounds(mutPos.get().set(chunkX, 64, chunkZ)); // Folia - region threading - } - - public boolean isChunkInBounds(int chunkX, int chunkZ) { -- this.mutPos.set(((chunkX << 4) + 15), 64, (chunkZ << 4) + 15); -- return this.isWithinBounds(this.mutPos); -+ return this.isWithinBounds(mutPos.get().set(((chunkX << 4) + 15), 64, (chunkZ << 4) + 15)); // Folia - region threading - } - // Paper end - Bound treasure maps to world border - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch deleted file mode 100644 index 8ee3c72..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -327,7 +_,7 @@ - } - - private static boolean tryAddReference(StructureManager structureManager, StructureStart structureStart) { -- if (structureStart.canBeReferenced()) { -+ if (structureStart.tryReference()) { // Folia - region threading - structureManager.addReference(structureStart); - return true; - } else { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch deleted file mode 100644 index 9e07081..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch +++ /dev/null @@ -1,117 +0,0 @@ ---- a/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/net/minecraft/world/level/chunk/LevelChunk.java -@@ -59,6 +_,13 @@ - public void tick() { - } - -+ // Folia start - region threading -+ @Override -+ public BlockEntity getTileEntity() { -+ return null; -+ } -+ // Folia end - region threading -+ - @Override - public boolean isRemoved() { - return true; -@@ -230,11 +_,7 @@ - - @Override - public void markUnsaved() { -- boolean isUnsaved = this.isUnsaved(); -- super.markUnsaved(); -- if (!isUnsaved) { -- this.unsavedListener.setUnsaved(this.chunkPos); -- } -+ super.markUnsaved(); // Folia - region threading - unsavedListener is not really use - } - - @Override -@@ -360,6 +_,7 @@ - - @Nullable - public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // Folia - region threading - // CraftBukkit end - int y = pos.getY(); - LevelChunkSection section = this.getSection(this.getSectionIndex(y)); -@@ -395,7 +_,7 @@ - } - - boolean hasBlockEntity = blockState.hasBlockEntity(); -- if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent -+ if (!this.level.isClientSide && !this.level.getCurrentWorldData().isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent // Folia - region threading - blockState.onRemove(this.level, pos, state, isMoving); - } else if (!blockState.is(block) && hasBlockEntity) { - this.removeBlockEntity(pos); -@@ -404,7 +_,7 @@ - if (!section.getBlockState(i, i1, i2).is(block)) { - return null; - } else { -- if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. -+ if (!this.level.isClientSide && doPlace && (!this.level.getCurrentWorldData().captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. // Folia - region threading - state.onPlace(this.level, pos, blockState, isMoving); - } - -@@ -459,7 +_,7 @@ - @Nullable - public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) { - // CraftBukkit start -- BlockEntity blockEntity = this.level.capturedTileEntities.get(pos); -+ BlockEntity blockEntity = this.level.getCurrentWorldData().capturedTileEntities.get(pos); // Folia - region threading - if (blockEntity == null) { - blockEntity = this.blockEntities.get(pos); - } -@@ -646,13 +_,13 @@ - - org.bukkit.World world = this.level.getWorld(); - if (world != null) { -- this.level.populating = true; -+ this.level.getCurrentWorldData().populating = true; // Folia - region threading - try { - for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) { - populator.populate(world, random, bukkitChunk); - } - } finally { -- this.level.populating = false; -+ this.level.getCurrentWorldData().populating = false; // Folia - region threading - } - } - server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); -@@ -678,7 +_,7 @@ - @Override - public boolean isUnsaved() { - // Paper start - rewrite chunk system -- final long gameTime = this.level.getGameTime(); -+ final long gameTime = this.level.getRedstoneGameTime(); // Folia - region threading - if (((ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks)this.blockTicks).moonrise$isDirty(gameTime) - || ((ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks)this.fluidTicks).moonrise$isDirty(gameTime)) { - return true; -@@ -905,6 +_,13 @@ - this.ticker = ticker; - } - -+ // Folia start - region threading -+ @Override -+ public BlockEntity getTileEntity() { -+ return this.blockEntity; -+ } -+ // Folia end - region threading -+ - @Override - public void tick() { - if (!this.blockEntity.isRemoved() && this.blockEntity.hasLevel()) { -@@ -982,6 +_,13 @@ - void rebind(TickingBlockEntity ticker) { - this.ticker = ticker; - } -+ -+ // Folia start - region threading -+ @Override -+ public BlockEntity getTileEntity() { -+ return this.ticker == null ? null : this.ticker.getTileEntity(); -+ } -+ // Folia end - region threading - - @Override - public void tick() { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch deleted file mode 100644 index 6ea9c0a..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/chunk/storage/SerializableChunkData.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -@@ -574,7 +_,7 @@ - } - } - -- ChunkAccess.PackedTicks ticksForSerialization = chunk.getTicksForSerialization(level.getGameTime()); -+ ChunkAccess.PackedTicks ticksForSerialization = chunk.getTicksForSerialization(level.getRedstoneGameTime()); // Folia - region threading - ShortList[] lists = Arrays.stream(chunk.getPostProcessing()) - .map(list3 -> list3 != null ? new ShortArrayList(list3) : null) - .toArray(ShortList[]::new); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch deleted file mode 100644 index 0b59d9a..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch +++ /dev/null @@ -1,65 +0,0 @@ ---- a/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -77,7 +_,7 @@ - .setPlayBossMusic(true) - .setCreateWorldFog(true); - public final ServerLevel level; -- private final BlockPos origin; -+ public final BlockPos origin; // Folia - region threading - public final ObjectArrayList gateways = new ObjectArrayList<>(); - private final BlockPattern exitPortalPattern; - private int ticksSinceDragonSeen; -@@ -162,7 +_,7 @@ - - if (!this.dragonEvent.getPlayers().isEmpty()) { - this.level.getChunkSource().addRegionTicket(TicketType.DRAGON, new ChunkPos(0, 0), 9, Unit.INSTANCE); -- boolean isArenaLoaded = this.isArenaLoaded(); -+ boolean isArenaLoaded = this.isArenaLoaded(); if (!isArenaLoaded) { return; } // Folia - region threading - don't tick if we don't own the entire region - if (this.needsStateScanning && isArenaLoaded) { - this.scanState(); - this.needsStateScanning = false; -@@ -208,6 +_,12 @@ - } - - List dragons = this.level.getDragons(); -+ // Folia start - region threading -+ // we do not want to deal with any dragons NOT nearby -+ dragons.removeIf((EnderDragon dragon) -> { -+ return !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(dragon); -+ }); -+ // Folia end - region threading - if (dragons.isEmpty()) { - this.dragonKilled = true; - } else { -@@ -323,8 +_,8 @@ - - for (int i = -8 + chunkPos.x; i <= 8 + chunkPos.x; i++) { - for (int i1 = 8 + chunkPos.z; i1 <= 8 + chunkPos.z; i1++) { -- ChunkAccess chunk = this.level.getChunk(i, i1, ChunkStatus.FULL, false); -- if (!(chunk instanceof LevelChunk)) { -+ ChunkAccess chunk = this.level.getChunkIfLoaded(i, i1); // Folia - region threading -+ if (!(chunk instanceof LevelChunk) || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, i, i1, this.level.regioniser.regionSectionChunkSize)) { - return false; - } - -@@ -496,6 +_,11 @@ - } - - public void onCrystalDestroyed(EndCrystal crystal, DamageSource dmgSrc) { -+ // Folia start - region threading -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, this.origin)) { -+ return; -+ } -+ // Folia end - region threading - if (this.respawnStage != null && this.respawnCrystals.contains(crystal)) { - LOGGER.debug("Aborting respawn sequence"); - this.respawnStage = null; -@@ -521,7 +_,7 @@ - - public boolean tryRespawn(@Nullable BlockPos placedEndCrystalPos) { // placedEndCrystalPos is null if the tryRespawn() call was not caused by a placed end crystal - // Paper end - Perf: Do crystal-portal proximity check before entity lookup -- if (this.dragonKilled && this.respawnStage == null) { -+ if (this.dragonKilled && this.respawnStage == null && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.level, this.origin)) { // Folia - region threading - BlockPos blockPos = this.portalLocation; - if (blockPos == null) { - LOGGER.debug("Tried to respawn, but need to find the portal first."); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch deleted file mode 100644 index 817bc3f..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch +++ /dev/null @@ -1,54 +0,0 @@ ---- a/net/minecraft/world/level/levelgen/PatrolSpawner.java -+++ b/net/minecraft/world/level/levelgen/PatrolSpawner.java -@@ -16,7 +_,7 @@ - import net.minecraft.world.level.block.state.BlockState; - - public class PatrolSpawner implements CustomSpawner { -- private int nextTick; -+ //private int nextTick; // Folia - region threading - - @Override - public int tick(ServerLevel level, boolean spawnEnemies, boolean spawnFriendlies) { -@@ -27,6 +_,7 @@ - return 0; - } else { - RandomSource randomSource = level.random; -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading - // this.nextTick--; - // if (this.nextTick > 0) { - // return 0; -@@ -38,12 +_,12 @@ - // } else if (randomSource.nextInt(5) != 0) { - // Paper start - Pillager patrol spawn settings and per player options - // Random player selection moved up for per player spawning and configuration -- int size = level.players().size(); -+ int size = level.getLocalPlayers().size(); - if (size < 1) { - return 0; - } - -- net.minecraft.server.level.ServerPlayer player = level.players().get(randomSource.nextInt(size)); -+ net.minecraft.server.level.ServerPlayer player = level.getLocalPlayers().get(randomSource.nextInt(size)); // Folia - region threading - if (player.isSpectator()) { - return 0; - } -@@ -53,8 +_,8 @@ - --player.patrolSpawnDelay; - patrolSpawnDelay = player.patrolSpawnDelay; - } else { -- this.nextTick--; -- patrolSpawnDelay = this.nextTick; -+ worldData.patrolSpawnerNextTick--; // Folia - region threading -+ patrolSpawnDelay = worldData.patrolSpawnerNextTick; // Folia - region threading - } - if (patrolSpawnDelay > 0) { - return 0; -@@ -68,7 +_,7 @@ - if (level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { - player.patrolSpawnDelay += level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomSource.nextInt(1200); - } else { -- this.nextTick += level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomSource.nextInt(1200); -+ worldData.patrolSpawnerNextTick += level.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomSource.nextInt(1200); // Folia - region threading - } - - if (days < level.paperConfig().entities.behavior.pillagerPatrols.start.day || !level.isDay()) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch deleted file mode 100644 index 66bce0e..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/PhantomSpawner.java.patch +++ /dev/null @@ -1,38 +0,0 @@ ---- a/net/minecraft/world/level/levelgen/PhantomSpawner.java -+++ b/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -19,7 +_,7 @@ - import net.minecraft.world.level.material.FluidState; - - public class PhantomSpawner implements CustomSpawner { -- private int nextTick; -+ //private int nextTick; // Folia - region threading - - @Override - public int tick(ServerLevel level, boolean spawnEnemies, boolean spawnFriendlies) { -@@ -34,21 +_,22 @@ - } - // Paper end - Ability to control player's insomnia and phantoms - RandomSource randomSource = level.random; -- this.nextTick--; -- if (this.nextTick > 0) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading -+ worldData.phantomSpawnerNextTick--; // Folia - region threading -+ if (worldData.phantomSpawnerNextTick > 0) { // Folia - region threading - return 0; - } else { - // Paper start - Ability to control player's insomnia and phantoms - int spawnAttemptMinSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMinSeconds; - int spawnAttemptMaxSeconds = level.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; -- this.nextTick += (spawnAttemptMinSeconds + randomSource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; -+ worldData.phantomSpawnerNextTick += (spawnAttemptMinSeconds + randomSource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; // Folia - region threading - // Paper end - Ability to control player's insomnia and phantoms - if (level.getSkyDarken() < 5 && level.dimensionType().hasSkyLight()) { - return 0; - } else { - int i = 0; - -- for (ServerPlayer serverPlayer : level.players()) { -+ for (ServerPlayer serverPlayer : level.getLocalPlayers()) { // Folia - region threading - if (!serverPlayer.isSpectator() && (!level.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !serverPlayer.isCreative())) { // Paper - Add phantom creative and insomniac controls - BlockPos blockPos = serverPlayer.blockPosition(); - if (!level.dimensionType().hasSkyLight() || blockPos.getY() >= level.getSeaLevel() && level.canSeeSky(blockPos)) { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch deleted file mode 100644 index ca02d11..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java -+++ b/net/minecraft/world/level/levelgen/feature/EndPlatformFeature.java -@@ -47,7 +_,7 @@ - - // CraftBukkit start - // SPIGOT-7746: Entity will only be null during world generation, which is async, so just generate without event -- if (entity != null) { -+ if (false) { // Folia - region threading - org.bukkit.World bworld = level.getLevel().getWorld(); - org.bukkit.event.world.PortalCreateEvent portalEvent = new org.bukkit.event.world.PortalCreateEvent((java.util.List) (java.util.List) blockList.getList(), bworld, entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM); - level.getLevel().getCraftServer().getPluginManager().callEvent(portalEvent); diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch deleted file mode 100644 index b124774..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch +++ /dev/null @@ -1,63 +0,0 @@ ---- a/net/minecraft/world/level/levelgen/structure/StructureStart.java -+++ b/net/minecraft/world/level/levelgen/structure/StructureStart.java -@@ -26,7 +_,7 @@ - private final Structure structure; - private final PiecesContainer pieceContainer; - private final ChunkPos chunkPos; -- private int references; -+ private final java.util.concurrent.atomic.AtomicInteger references; // Folia - region threading - @Nullable - private volatile BoundingBox cachedBoundingBox; - -@@ -39,7 +_,7 @@ - public StructureStart(Structure structure, ChunkPos chunkPos, int references, PiecesContainer pieceContainer) { - this.structure = structure; - this.chunkPos = chunkPos; -- this.references = references; -+ this.references = new java.util.concurrent.atomic.AtomicInteger(references); // Folia - region threading - this.pieceContainer = pieceContainer; - } - -@@ -126,7 +_,7 @@ - compoundTag.putString("id", context.registryAccess().lookupOrThrow(Registries.STRUCTURE).getKey(this.structure).toString()); - compoundTag.putInt("ChunkX", chunkPos.x); - compoundTag.putInt("ChunkZ", chunkPos.z); -- compoundTag.putInt("references", this.references); -+ compoundTag.putInt("references", this.references.get()); // Folia - region threading - compoundTag.put("Children", this.pieceContainer.save(context)); - return compoundTag; - } else { -@@ -144,15 +_,29 @@ - } - - public boolean canBeReferenced() { -- return this.references < this.getMaxReferences(); -- } -+ throw new UnsupportedOperationException("Use tryReference()"); // Folia - region threading -+ } -+ -+ // Folia start - region threading -+ public boolean tryReference() { -+ for (int curr = this.references.get();;) { -+ if (curr >= this.getMaxReferences()) { -+ return false; -+ } -+ -+ if (curr == (curr = this.references.compareAndExchange(curr, curr + 1))) { -+ return true; -+ } // else: try again -+ } -+ } -+ // Folia end - region threading - - public void addReference() { -- this.references++; -+ throw new UnsupportedOperationException("Use tryReference()"); // Folia - region threading - } - - public int getReferences() { -- return this.references; -+ return this.references.get(); // Folia - region threading - } - - protected int getMaxReferences() { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch deleted file mode 100644 index 3acbf63..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java -+++ b/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java -@@ -47,6 +_,7 @@ - } - - private void addAndRun(BlockPos pos, CollectingNeighborUpdater.NeighborUpdates updates) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((net.minecraft.server.level.ServerLevel)this.level, pos, "Adding block without owning region"); // Folia - region threading - boolean flag = this.count > 0; - boolean flag1 = this.maxChainedNeighborUpdates >= 0 && this.count >= this.maxChainedNeighborUpdates; - this.count++; diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/SavedData.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/SavedData.java.patch deleted file mode 100644 index bae465b..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/SavedData.java.patch +++ /dev/null @@ -1,23 +0,0 @@ ---- a/net/minecraft/world/level/saveddata/SavedData.java -+++ b/net/minecraft/world/level/saveddata/SavedData.java -@@ -8,7 +_,7 @@ - import net.minecraft.util.datafix.DataFixTypes; - - public abstract class SavedData { -- private boolean dirty; -+ private volatile boolean dirty; // Folia - make map data thread-safe - - public abstract CompoundTag save(CompoundTag tag, HolderLookup.Provider registries); - -@@ -26,9 +_,10 @@ - - public CompoundTag save(HolderLookup.Provider registries) { - CompoundTag compoundTag = new CompoundTag(); -+ this.setDirty(false); // Folia - make map data thread-safe - move before save, so that any changes after are not lost - compoundTag.put("data", this.save(new CompoundTag(), registries)); - NbtUtils.addCurrentDataVersion(compoundTag); -- this.setDirty(false); -+ // Folia - make map data thread-safe - move before save, so that any changes after are not lost - return compoundTag; - } - diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/maps/MapIndex.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/maps/MapIndex.java.patch deleted file mode 100644 index bb7c8c7..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/maps/MapIndex.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/net/minecraft/world/level/saveddata/maps/MapIndex.java -+++ b/net/minecraft/world/level/saveddata/maps/MapIndex.java -@@ -34,17 +_,21 @@ - - @Override - public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { -+ synchronized (this.usedAuxIds) { // Folia - make map data thread-safe - for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { - tag.putInt(entry.getKey(), entry.getIntValue()); - } -+ } // Folia - make map data thread-safe - - return tag; - } - - public MapId getFreeAuxValueForMap() { -+ synchronized (this.usedAuxIds) { // Folia - make map data thread-safe - int i = this.usedAuxIds.getInt("map") + 1; - this.usedAuxIds.put("map", i); - this.setDirty(); - return new MapId(i); -+ } // Folia - make map data thread-safe - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch deleted file mode 100644 index 5bbb012..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch +++ /dev/null @@ -1,172 +0,0 @@ ---- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -+++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -@@ -201,7 +_,7 @@ - } - - @Override -- public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { -+ public synchronized CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { // Folia - make map data thread-safe - ResourceLocation.CODEC - .encodeStart(NbtOps.INSTANCE, this.dimension.location()) - .resultOrPartial(LOGGER::error) -@@ -244,7 +_,7 @@ - return tag; - } - -- public MapItemSavedData locked() { -+ public synchronized MapItemSavedData locked() { // Folia - make map data thread-safe - MapItemSavedData mapItemSavedData = new MapItemSavedData( - this.centerX, this.centerZ, this.scale, this.trackingPosition, this.unlimitedTracking, true, this.dimension - ); -@@ -255,7 +_,7 @@ - return mapItemSavedData; - } - -- public MapItemSavedData scaled() { -+ public synchronized MapItemSavedData scaled() { // Folia - make map data thread-safe - return createFresh(this.centerX, this.centerZ, (byte)Mth.clamp(this.scale + 1, 0, 4), this.trackingPosition, this.unlimitedTracking, this.dimension); - } - -@@ -264,7 +_,8 @@ - return itemStack -> itemStack == stack || itemStack.is(stack.getItem()) && Objects.equals(mapId, itemStack.get(DataComponents.MAP_ID)); - } - -- public void tickCarriedBy(Player player, ItemStack mapStack) { -+ public synchronized void tickCarriedBy(Player player, ItemStack mapStack) { // Folia - make map data thread-safe -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(player, "Ticking map player in incorrect region"); // Folia - region threading - if (!this.carriedByPlayers.containsKey(player)) { - MapItemSavedData.HoldingPlayer holdingPlayer = new MapItemSavedData.HoldingPlayer(player); - this.carriedByPlayers.put(player, holdingPlayer); -@@ -413,7 +_,7 @@ - - private byte calculateRotation(@Nullable LevelAccessor level, double yRot) { - if (this.dimension == Level.NETHER && level != null) { -- int i = (int)(level.getLevelData().getDayTime() / 10L); -+ int i = (int)(level.dayTime() / 10L); // Folia - region threading - return (byte)(i * i * 34187121 + i * 121 >> 15 & 15); - } else { - double d = yRot < 0.0 ? yRot - 8.0 : yRot + 8.0; -@@ -447,25 +_,27 @@ - } - - @Nullable -- public Packet getUpdatePacket(MapId mapId, Player player) { -+ public synchronized Packet getUpdatePacket(MapId mapId, Player player) { // Folia - make map data thread-safe - MapItemSavedData.HoldingPlayer holdingPlayer = this.carriedByPlayers.get(player); - return holdingPlayer == null ? null : holdingPlayer.nextUpdatePacket(mapId); - } - -- public void setColorsDirty(int x, int z) { -- this.setDirty(); -+ public synchronized void setColorsDirty(int x, int z) { // Folia - make map data thread-safe -+ //this.setDirty(); // Folia - make dirty only after updating data - moved down - - for (MapItemSavedData.HoldingPlayer holdingPlayer : this.carriedBy) { - holdingPlayer.markColorsDirty(x, z); - } -+ this.setDirty(); // Folia - make dirty only after updating data - moved from above - } - -- public void setDecorationsDirty() { -- this.setDirty(); -+ public synchronized void setDecorationsDirty() { // Folia - make map data thread-safe -+ //this.setDirty(); // Folia - make dirty only after updating data - moved down - this.carriedBy.forEach(MapItemSavedData.HoldingPlayer::markDecorationsDirty); -+ this.setDirty(); // Folia - make dirty only after updating data - moved from above - } - -- public MapItemSavedData.HoldingPlayer getHoldingPlayer(Player player) { -+ public synchronized MapItemSavedData.HoldingPlayer getHoldingPlayer(Player player) { // Folia - make map data thread-safe - MapItemSavedData.HoldingPlayer holdingPlayer = this.carriedByPlayers.get(player); - if (holdingPlayer == null) { - holdingPlayer = new MapItemSavedData.HoldingPlayer(player); -@@ -476,7 +_,7 @@ - return holdingPlayer; - } - -- public boolean toggleBanner(LevelAccessor accessor, BlockPos pos) { -+ public synchronized boolean toggleBanner(LevelAccessor accessor, BlockPos pos) { // Folia - make map data thread-safe - double d = pos.getX() + 0.5; - double d1 = pos.getZ() + 0.5; - int i = 1 << this.scale; -@@ -484,7 +_,7 @@ - double d3 = (d1 - this.centerZ) / i; - int i1 = 63; - if (d2 >= -63.0 && d3 >= -63.0 && d2 <= 63.0 && d3 <= 63.0) { -- MapBanner mapBanner = MapBanner.fromWorld(accessor, pos); -+ MapBanner mapBanner = accessor.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null || !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(accessor.getMinecraftWorld(), pos) ? null : MapBanner.fromWorld(accessor, pos); // Folia - make map data thread-safe - don't sync load or read data we do not own - if (mapBanner == null) { - return false; - } -@@ -504,7 +_,7 @@ - return false; - } - -- public void checkBanners(BlockGetter reader, int x, int z) { -+ public synchronized void checkBanners(BlockGetter reader, int x, int z) { // Folia - make map data thread-safe - Iterator iterator = this.bannerMarkers.values().iterator(); - - while (iterator.hasNext()) { -@@ -523,13 +_,13 @@ - return this.bannerMarkers.values(); - } - -- public void removedFromFrame(BlockPos pos, int entityId) { -+ public synchronized void removedFromFrame(BlockPos pos, int entityId) { // Folia - make map data thread-safe - this.removeDecoration(getFrameKey(entityId)); - this.frameMarkers.remove(MapFrame.frameId(pos)); - this.setDirty(); - } - -- public boolean updateColor(int x, int z, byte color) { -+ public synchronized boolean updateColor(int x, int z, byte color) { // Folia - make map data thread-safe - byte b = this.colors[x + z * 128]; - if (b != color) { - this.setColor(x, z, color); -@@ -539,12 +_,12 @@ - } - } - -- public void setColor(int x, int z, byte color) { -+ public synchronized void setColor(int x, int z, byte color) { // Folia - make map data thread-safe - this.colors[x + z * 128] = color; - this.setColorsDirty(x, z); - } - -- public boolean isExplorationMap() { -+ public synchronized boolean isExplorationMap() { // Folia - make map data thread-safe - for (MapDecoration mapDecoration : this.decorations.values()) { - if (mapDecoration.type().value().explorationMapElement()) { - return true; -@@ -554,7 +_,7 @@ - return false; - } - -- public void addClientSideDecorations(List decorations) { -+ public synchronized void addClientSideDecorations(List decorations) { // Folia - make map data thread-safe - this.decorations.clear(); - this.trackedDecorationCount = 0; - -@@ -571,7 +_,7 @@ - return this.decorations.values(); - } - -- public boolean isTrackedCountOverLimit(int trackedCount) { -+ public synchronized boolean isTrackedCountOverLimit(int trackedCount) { // Folia - make map data thread-safe - return this.trackedDecorationCount >= trackedCount; - } - -@@ -726,11 +_,13 @@ - } - - public void applyToMap(MapItemSavedData savedData) { -+ synchronized (savedData) { // Folia - make map data thread-safe - for (int i = 0; i < this.width; i++) { - for (int i1 = 0; i1 < this.height; i1++) { - savedData.setColor(this.startX + i, this.startY + i1, this.mapColors[i + i1 * this.width]); - } - } -+ } // Folia - make map data thread-safe - } - } - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch deleted file mode 100644 index 40a48a1..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/level/storage/DimensionDataStorage.java.patch +++ /dev/null @@ -1,42 +0,0 @@ ---- a/net/minecraft/world/level/storage/DimensionDataStorage.java -+++ b/net/minecraft/world/level/storage/DimensionDataStorage.java -@@ -51,6 +_,7 @@ - } - - public T computeIfAbsent(SavedData.Factory factory, String name) { -+ synchronized (this.cache) { // Folia - make map data thread-safe - T savedData = this.get(factory, name); - if (savedData != null) { - return savedData; -@@ -59,10 +_,12 @@ - this.set(name, savedData1); - return savedData1; - } -+ } // Folia - make map data thread-safe - } - - @Nullable - public T get(SavedData.Factory factory, String name) { -+ synchronized (this.cache) { // Folia - make map data thread-safe - Optional optional = this.cache.get(name); - if (optional == null) { - optional = Optional.ofNullable(this.readSavedData(factory.deserializer(), factory.type(), name)); -@@ -70,6 +_,7 @@ - } - - return (T)optional.orElse(null); -+ } // Folia - make map data thread-safe - } - - @Nullable -@@ -88,8 +_,10 @@ - } - - public void set(String name, SavedData savedData) { -+ synchronized (this.cache) { // Folia - make map data thread-safe - this.cache.put(name, Optional.of(savedData)); - savedData.setDirty(); -+ } // Folia - make map data thread-safe - } - - public CompoundTag readTagFromDisk(String filename, DataFixTypes dataFixType, int version) throws IOException { diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/ticks/LevelChunkTicks.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/ticks/LevelChunkTicks.java.patch deleted file mode 100644 index aaa148a..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/ticks/LevelChunkTicks.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/net/minecraft/world/ticks/LevelChunkTicks.java -+++ b/net/minecraft/world/ticks/LevelChunkTicks.java -@@ -48,6 +_,21 @@ - this.dirty = false; - } - // Paper end - rewrite chunk system -+ // Folia start - region threading -+ public void offsetTicks(final long offset) { -+ if (offset == 0 || this.tickQueue.isEmpty()) { -+ return; -+ } -+ final ScheduledTick[] queue = this.tickQueue.toArray(new ScheduledTick[0]); -+ this.tickQueue.clear(); -+ for (final ScheduledTick entry : queue) { -+ final ScheduledTick newEntry = new ScheduledTick<>( -+ entry.type(), entry.pos(), entry.triggerTick() + offset, entry.subTickOrder() -+ ); -+ this.tickQueue.add(newEntry); -+ } -+ } -+ // Folia end - region threading - - public LevelChunkTicks() { - } diff --git a/folia-server/minecraft-patches/sources/net/minecraft/world/ticks/LevelTicks.java.patch b/folia-server/minecraft-patches/sources/net/minecraft/world/ticks/LevelTicks.java.patch deleted file mode 100644 index 19d94eb..0000000 --- a/folia-server/minecraft-patches/sources/net/minecraft/world/ticks/LevelTicks.java.patch +++ /dev/null @@ -1,102 +0,0 @@ ---- a/net/minecraft/world/ticks/LevelTicks.java -+++ b/net/minecraft/world/ticks/LevelTicks.java -@@ -39,12 +_,69 @@ - private final List> alreadyRunThisTick = new ArrayList<>(); - private final Set> toRunThisTickSet = new ObjectOpenCustomHashSet<>(ScheduledTick.UNIQUE_TICK_HASH); - private final BiConsumer, ScheduledTick> chunkScheduleUpdater = (levelChunkTicks, scheduledTick) -> { -- if (scheduledTick.equals(levelChunkTicks.peek())) { -- this.updateContainerScheduling(scheduledTick); -+ if (scheduledTick.equals(levelChunkTicks.peek())) { // Folia - diff on change -+ this.updateContainerScheduling(scheduledTick); // Folia - diff on change - } - }; - -- public LevelTicks(LongPredicate tickCheck) { -+ // Folia start - region threading -+ public final net.minecraft.server.level.ServerLevel world; -+ public final boolean isBlock; -+ -+ public void merge(final LevelTicks into, final long tickOffset) { -+ // note: containersToTick, toRunThisTick, alreadyRunThisTick, toRunThisTickSet -+ // are all transient state, only ever non-empty during tick. But merging regions occurs while there -+ // is no tick happening, so we assume they are empty. -+ for (final java.util.Iterator>> iterator = -+ ((Long2ObjectOpenHashMap>)this.allContainers).long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry> entry = iterator.next(); -+ final LevelChunkTicks tickContainer = entry.getValue(); -+ tickContainer.offsetTicks(tickOffset); -+ into.allContainers.put(entry.getLongKey(), tickContainer); -+ } -+ for (final java.util.Iterator iterator = ((Long2LongOpenHashMap)this.nextTickForContainer).long2LongEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2LongMap.Entry entry = iterator.next(); -+ into.nextTickForContainer.put(entry.getLongKey(), entry.getLongValue() + tickOffset); -+ } -+ } -+ -+ public void split(final int chunkToRegionShift, -+ final it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap> regionToData) { -+ for (final java.util.Iterator>> iterator = -+ ((Long2ObjectOpenHashMap>)this.allContainers).long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry> entry = iterator.next(); -+ -+ final long chunkKey = entry.getLongKey(); -+ final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkKey); -+ -+ final long regionSectionKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey( -+ chunkX >> chunkToRegionShift, chunkZ >> chunkToRegionShift -+ ); -+ // Should always be non-null, since containers are removed on unload. -+ regionToData.get(regionSectionKey).allContainers.put(chunkKey, entry.getValue()); -+ } -+ for (final java.util.Iterator iterator = ((Long2LongOpenHashMap)this.nextTickForContainer).long2LongEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2LongMap.Entry entry = iterator.next(); -+ final long chunkKey = entry.getLongKey(); -+ final int chunkX = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkKey); -+ -+ final long regionSectionKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey( -+ chunkX >> chunkToRegionShift, chunkZ >> chunkToRegionShift -+ ); -+ -+ // Should always be non-null, since containers are removed on unload. -+ regionToData.get(regionSectionKey).nextTickForContainer.put(chunkKey, entry.getLongValue()); -+ } -+ } -+ // Folia end - region threading -+ -+ public LevelTicks(LongPredicate tickCheck, net.minecraft.server.level.ServerLevel world, boolean isBlock) { this.world = world; this.isBlock = isBlock; // Folia - add world and isBlock - this.tickCheck = tickCheck; - } - -@@ -56,7 +_,17 @@ - this.nextTickForContainer.put(packedChunkPos, scheduledTick.triggerTick()); - } - -- chunkTicks.setOnTickAdded(this.chunkScheduleUpdater); -+ // Folia start - region threading -+ final boolean isBlock = this.isBlock; -+ final net.minecraft.server.level.ServerLevel world = this.world; -+ // make sure the lambda contains no reference to this LevelTicks -+ chunkTicks.setOnTickAdded((LevelChunkTicks levelChunkTicks, ScheduledTick tick) -> { -+ if (tick.equals(levelChunkTicks.peek())) { -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); -+ ((LevelTicks)(isBlock ? worldData.getBlockLevelTicks() : worldData.getFluidLevelTicks())).updateContainerScheduling(tick); -+ } -+ }); -+ // Folia end - region threading - } - - public void removeContainer(ChunkPos chunkPos) { -@@ -70,6 +_,7 @@ - - @Override - public void schedule(ScheduledTick tick) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, tick.pos(), "Cannot schedule tick for another region!"); // Folia - region threading - long packedChunkPos = ChunkPos.asLong(tick.pos()); - LevelChunkTicks levelChunkTicks = this.allContainers.get(packedChunkPos); - if (levelChunkTicks == null) { diff --git a/folia-server/paper-patches/features/0001-Region-Threading-Base.patch b/folia-server/paper-patches/features/0001-Region-Threading-Base.patch new file mode 100644 index 0000000..c49d5ff --- /dev/null +++ b/folia-server/paper-patches/features/0001-Region-Threading-Base.patch @@ -0,0 +1,5332 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Apr 1997 05:37:42 -0800 +Subject: [PATCH] Region Threading Base + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..c95769a4e64fabd7acdff6c5f6f349107e1cf5c0 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +@@ -1,5 +1,11 @@ + package ca.spottedleaf.moonrise.common.util; + ++import io.papermc.paper.threadedregions.RegionShutdownThread; ++import io.papermc.paper.threadedregions.RegionizedServer; ++import io.papermc.paper.threadedregions.RegionizedWorldData; ++import io.papermc.paper.threadedregions.ThreadedRegionizer; ++import io.papermc.paper.threadedregions.TickRegionScheduler; ++import io.papermc.paper.threadedregions.TickRegions; + import net.minecraft.core.BlockPos; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.level.ChunkPos; +@@ -15,8 +21,26 @@ public class TickThread extends Thread { + + private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); + ++ private static String getRegionInfo(final ThreadedRegionizer.ThreadedRegion region) { ++ if (region == null) { ++ return "{null}"; ++ } ++ ++ final ChunkPos center = region.getCenterChunk(); ++ final net.minecraft.server.level.ServerLevel world = region.regioniser.world; ++ ++ return "{center=" + center + ",world=" + (world == null ? "null" : WorldUtil.getWorldName(world)) + "}"; ++ } ++ + private static String getThreadContext() { +- return "thread=" + Thread.currentThread().getName(); ++ final Thread thread = Thread.currentThread(); ++ ++ if (!(thread instanceof TickThread)) { ++ return "[thread=" + thread + ",class=" + thread.getClass().getName() + "]"; ++ } ++ ++ return "[thread=" + thread.getName() + ",class=" + thread.getClass().getName() + ",region=" + getRegionInfo(TickRegionScheduler.getCurrentRegion()) + "]"; ++ + } + + /** +@@ -123,50 +147,157 @@ public class TickThread extends Thread { + } + + public static boolean isShutdownThread() { +- return false; ++ return Thread.currentThread().getClass() == RegionShutdownThread.class; + } + + public static boolean isTickThreadFor(final Level world, final BlockPos pos) { +- return isTickThread(); ++ return isTickThreadFor(world, pos.getX() >> 4, pos.getZ() >> 4); + } + + public static boolean isTickThreadFor(final Level world, final BlockPos pos, final int blockRadius) { +- return isTickThread(); ++ return isTickThreadFor( ++ world, ++ (pos.getX() - blockRadius) >> 4, (pos.getZ() - blockRadius) >> 4, ++ (pos.getX() + blockRadius) >> 4, (pos.getZ() + blockRadius) >> 4 ++ ); + } + + public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { +- return isTickThread(); ++ return isTickThreadFor(world, pos.x, pos.z); + } + + public static boolean isTickThreadFor(final Level world, final Vec3 pos) { +- return isTickThread(); ++ return isTickThreadFor(world, net.minecraft.util.Mth.floor(pos.x) >> 4, net.minecraft.util.Mth.floor(pos.z) >> 4); + } + + public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { +- return isTickThread(); ++ final ThreadedRegionizer.ThreadedRegion region = ++ TickRegionScheduler.getCurrentRegion(); ++ if (region == null) { ++ return isShutdownThread(); ++ } ++ return ((net.minecraft.server.level.ServerLevel)world).regioniser.getRegionAtUnsynchronised(chunkX, chunkZ) == region; + } + + public static boolean isTickThreadFor(final Level world, final AABB aabb) { +- return isTickThread(); ++ return isTickThreadFor( ++ world, ++ CoordinateUtils.getChunkCoordinate(aabb.minX), CoordinateUtils.getChunkCoordinate(aabb.minZ), ++ CoordinateUtils.getChunkCoordinate(aabb.maxX), CoordinateUtils.getChunkCoordinate(aabb.maxZ) ++ ); + } + + public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { +- return isTickThread(); ++ return isTickThreadFor(world, CoordinateUtils.getChunkCoordinate(blockX), CoordinateUtils.getChunkCoordinate(blockZ)); + } + + public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { +- return isTickThread(); ++ final int fromChunkX = CoordinateUtils.getChunkX(position); ++ final int fromChunkZ = CoordinateUtils.getChunkZ(position); ++ ++ final int toChunkX = CoordinateUtils.getChunkCoordinate(position.x + deltaMovement.x); ++ final int toChunkZ = CoordinateUtils.getChunkCoordinate(position.z + deltaMovement.z); ++ ++ // expect from < to, but that may not be the case ++ return isTickThreadFor( ++ world, ++ Math.min(fromChunkX, toChunkX) - buffer, ++ Math.min(fromChunkZ, toChunkZ) - buffer, ++ Math.max(fromChunkX, toChunkX) + buffer, ++ Math.max(fromChunkZ, toChunkZ) + buffer ++ ); + } + + public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { +- return isTickThread(); ++ final ThreadedRegionizer.ThreadedRegion region = ++ TickRegionScheduler.getCurrentRegion(); ++ if (region == null) { ++ return isShutdownThread(); ++ } ++ ++ final int shift = ((net.minecraft.server.level.ServerLevel)world).regioniser.sectionChunkShift; ++ ++ final int minSectionX = fromChunkX >> shift; ++ final int maxSectionX = toChunkX >> shift; ++ final int minSectionZ = fromChunkZ >> shift; ++ final int maxSectionZ = toChunkZ >> shift; ++ ++ for (int secZ = minSectionZ; secZ <= maxSectionZ; ++secZ) { ++ for (int secX = minSectionX; secX <= maxSectionX; ++secX) { ++ final int lowerLeftCX = secX << shift; ++ final int lowerLeftCZ = secZ << shift; ++ if (((net.minecraft.server.level.ServerLevel)world).regioniser.getRegionAtUnsynchronised(lowerLeftCX, lowerLeftCZ) != region) { ++ return false; ++ } ++ } ++ } ++ ++ return true; + } + + public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { +- return isTickThread(); ++ return isTickThreadFor(world, chunkX - radius, chunkZ - radius, chunkX + radius, chunkZ + radius); + } + + public static boolean isTickThreadFor(final Entity entity) { +- return isTickThread(); ++ if (entity == null) { ++ return true; ++ } ++ final ThreadedRegionizer.ThreadedRegion region = ++ TickRegionScheduler.getCurrentRegion(); ++ if (region == null) { ++ if (RegionizedServer.isGlobalTickThread()) { ++ if (entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ final net.minecraft.server.network.ServerGamePacketListenerImpl possibleBad = serverPlayer.connection; ++ if (possibleBad == null) { ++ return true; ++ } ++ ++ final net.minecraft.network.PacketListener packetListener = possibleBad.connection.getPacketListener(); ++ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl gamePacketListener) { ++ return gamePacketListener.waitingForSwitchToConfig; ++ } ++ if (packetListener instanceof net.minecraft.server.network.ServerConfigurationPacketListenerImpl configurationPacketListener) { ++ return !configurationPacketListener.switchToMain; ++ } ++ return true; ++ } else { ++ return false; ++ } ++ } ++ if (isShutdownThread()) { ++ return true; ++ } ++ if (entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ // off-main access to server player is never ok, server player is owned by one of global context or region context always ++ return false; ++ } ++ // only own entities that have not yet been added to the world ++ ++ // if the entity is removed, then it was in the world previously - which means that a region containing its location ++ // owns it ++ // if the entity has a callback, then it is contained in a world ++ return entity.hasNullCallback() && !entity.isRemoved(); ++ } ++ ++ final Level world = entity.level(); ++ if (world != region.regioniser.world) { ++ // world mismatch ++ return false; ++ } ++ ++ final RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); ++ ++ // pass through the check if the entity is removed and we own its chunk ++ if (worldData.hasEntity(entity)) { ++ return true; ++ } ++ ++ if (entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ net.minecraft.server.network.ServerGamePacketListenerImpl conn = serverPlayer.connection; ++ return conn != null && worldData.connections.contains(conn.connection); ++ } else { ++ return ((entity.hasNullCallback() || entity.isRemoved())) && isTickThreadFor((net.minecraft.server.level.ServerLevel)world, entity.chunkPosition()); ++ } + } + } +diff --git a/src/main/java/io/papermc/paper/SparksFly.java b/src/main/java/io/papermc/paper/SparksFly.java +index 62e2d5704c348955bc8284dc2d54c933b7bcdd06..b332645ed65928100f580221d8a9948bc77e362e 100644 +--- a/src/main/java/io/papermc/paper/SparksFly.java ++++ b/src/main/java/io/papermc/paper/SparksFly.java +@@ -33,13 +33,13 @@ public final class SparksFly { + + private final Logger logger; + private final PaperSparkModule spark; +- private final ConcurrentLinkedQueue mainThreadTaskQueue; ++ // Folia - region threading + + private boolean enabled; + private boolean disabledInConfigurationWarningLogged; + + public SparksFly(final Server server) { +- this.mainThreadTaskQueue = new ConcurrentLinkedQueue<>(); ++ // Folia - region threading + this.logger = Logger.getLogger(ID); + this.logger.log(Level.INFO, "This server bundles the spark profiler. For more information please visit https://docs.papermc.io/paper/profiling"); + this.spark = PaperSparkModule.create(Compatibility.VERSION_1_0, server, this.logger, new PaperScheduler() { +@@ -50,7 +50,7 @@ public final class SparksFly { + + @Override + public void executeSync(final Runnable runnable) { +- SparksFly.this.mainThreadTaskQueue.offer(this.catching(runnable, "synchronous")); ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(this.catching(runnable, "synchronous")); // Folia - region threading + } + + private Runnable catching(final Runnable runnable, final String type) { +@@ -88,10 +88,7 @@ public final class SparksFly { + } + + public void executeMainThreadTasks() { +- Runnable task; +- while ((task = this.mainThreadTaskQueue.poll()) != null) { +- task.run(); +- } ++ throw new UnsupportedOperationException(); // Folia - region threading + } + + public void enableEarlyIfRequested() { +@@ -119,7 +116,7 @@ public final class SparksFly { + + private void enable() { + if (!this.enabled) { +- if (GlobalConfiguration.get().spark.enabled) { ++ if (false) { // Folia - disable in-built spark profiler + this.enabled = true; + this.spark.enable(); + } else { +@@ -171,7 +168,7 @@ public final class SparksFly { + } + + public static boolean isPluginPreferred() { +- return Boolean.getBoolean(PREFER_SPARK_PLUGIN_PROPERTY); ++ return true; // Folia - disable in-built spark profiler + } + + private static boolean isPluginEnabled(final Server server) { +diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +index 14e412ebf75b0e06ab53a1c8f9dd1be6ad1e2680..3f733319482fedcf7461f4b7466e84afeae1fc2b 100644 +--- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java ++++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +@@ -83,7 +83,7 @@ public final class ChatProcessor { + final CraftPlayer player = this.player.getBukkitEntity(); + final AsyncPlayerChatEvent ae = new AsyncPlayerChatEvent(this.async, player, this.craftbukkit$originalMessage, new LazyPlayerSet(this.server)); + this.post(ae); +- if (listenersOnSyncEvent) { ++ if (false && listenersOnSyncEvent) { // Folia - region threading + final PlayerChatEvent se = new PlayerChatEvent(player, ae.getMessage(), ae.getFormat(), ae.getRecipients()); + se.setCancelled(ae.isCancelled()); // propagate cancelled state + this.queueIfAsyncOrRunImmediately(new Waitable() { +@@ -150,7 +150,7 @@ public final class ChatProcessor { + ae.setCancelled(cancelled); // propagate cancelled state + this.post(ae); + final boolean listenersOnSyncEvent = canYouHearMe(ChatEvent.getHandlerList()); +- if (listenersOnSyncEvent) { ++ if (false && listenersOnSyncEvent) { // Folia - region threading + this.queueIfAsyncOrRunImmediately(new Waitable() { + @Override + protected Void evaluate() { +diff --git a/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java +index 23432eea862c6df716d7726a32da3a0612a3fb77..f59e8bb72c5233f26a8a0d506ac64bb37fef97a5 100644 +--- a/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java ++++ b/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java +@@ -23,35 +23,42 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider { + + public static final class CallbackManager { + +- private final Map callbacks = new HashMap<>(); +- private final Queue queue = new ConcurrentLinkedQueue<>(); ++ private final java.util.concurrent.ConcurrentHashMap callbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - region threading ++ // Folia - region threading + + private CallbackManager() { + } + + public UUID addCallback(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { + final UUID id = UUID.randomUUID(); +- this.queue.add(new StoredCallback(callback, options, id)); ++ final StoredCallback scb = new StoredCallback(callback, options, id); // Folia - region threading ++ this.callbacks.put(scb.id(), scb); // Folia - region threading + return id; + } + + public void handleQueue(final int currentTick) { + // Evict expired entries + if (currentTick % 100 == 0) { +- this.callbacks.values().removeIf(callback -> !callback.valid()); ++ this.callbacks.values().removeIf(StoredCallback::expired); // Folia - region threading - don't read uses field + } + +- // Add entries from queue +- StoredCallback callback; +- while ((callback = this.queue.poll()) != null) { +- this.callbacks.put(callback.id(), callback); +- } ++ // Folia - region threading + } + + public void runCallback(final @NotNull Audience audience, final UUID id) { +- final StoredCallback callback = this.callbacks.get(id); +- if (callback != null && callback.valid()) { //TODO Message if expired/invalid? +- callback.takeUse(); ++ // Folia start - region threading ++ final StoredCallback[] use = new StoredCallback[1]; ++ this.callbacks.computeIfPresent(id, (final UUID keyInMap, final StoredCallback value) -> { ++ if (!value.valid()) { ++ return null; ++ } ++ use[0] = value; ++ value.takeUse(); ++ return value.valid() ? value : null; ++ }); ++ final StoredCallback callback = use[0]; ++ if (callback != null) { //TODO Message if expired/invalid? ++ // Folia end - region threading + callback.callback.accept(audience); + } + } +diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java +index 7b58b2d6297800c2dcdbf7539e5ab8e7703f39f1..5ca37a31d97a0fd068bf4ca3804456c91a6059f4 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommands.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommands.java +@@ -18,7 +18,7 @@ public final class PaperCommands { + static { + COMMANDS.put("paper", new PaperCommand("paper")); + COMMANDS.put("callback", new CallbackCommand("callback")); +- COMMANDS.put("mspt", new MSPTCommand("mspt")); ++ COMMANDS.put("tps", new io.papermc.paper.threadedregions.commands.CommandServerHealth()); // Folia - region threading + } + + public static void registerCommands(final MinecraftServer server) { +diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +index bbd29bcca94a81ad2603afa9ddcb160e925b405e..6284384299000480e9d5c0b62e8d88b2c922e776 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +@@ -129,7 +129,7 @@ public final class EntityCommand implements PaperSubcommand { + final int z = (e.getKey().z << 4) + 8; + final Component message = text(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)")) + .hoverEvent(HoverEvent.showText(text("Click to teleport to chunk", GREEN))) +- .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey() + " run tp " + x + " " + (world.getWorld().getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING) + 1) + " " + z)); ++ .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey() + " run tp " + x + " " + (128) + " " + z)); // Folia - region threading - avoid sync load here + sender.sendMessage(message); + }); + } else { +diff --git a/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +index cd2e4d792e972b8bf1e07b8961594a670ae949cf..3ab8dbf2768a4ef8fb53af6f5431f7f6afe6d168 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java +@@ -18,7 +18,9 @@ import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; + public final class HeapDumpCommand implements PaperSubcommand { + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading + this.dumpHeap(sender); ++ }); // Folia - region threading + return true; + } + +diff --git a/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +index bd68139ae635f2ad7ec8e7a21e0056a139c4c62e..48a43341b17247355a531164019d5cc9c5555f26 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java +@@ -16,7 +16,9 @@ import static net.kyori.adventure.text.format.NamedTextColor.RED; + public final class ReloadCommand implements PaperSubcommand { + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading + this.doReload(sender); ++ }); // Folia - region threading + return true; + } + +diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +index 42777adb028fe282c1619aeb5431c442ad5df0d0..de88c3d9d3523a7bd3f3dcbfc62d72658192521d 100644 +--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +@@ -398,4 +398,17 @@ public class GlobalConfiguration extends ConfigurationPart { + } + } + } ++ // Folia start - threaded regions ++ public ThreadedRegions threadedRegions; ++ public class ThreadedRegions extends ConfigurationPart { ++ ++ public int threads = -1; ++ public int gridExponent = 4; ++ ++ @PostProcess ++ public void postProcess() { ++ io.papermc.paper.threadedregions.TickRegions.init(this); ++ } ++ } ++ // Folia end - threaded regions + } +diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +index d7c9acaffdcff5e35e026ae90a3e521bab13b074..321c4cad4f84c57f56e5bec3bcdbbef8823f70fe 100644 +--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +@@ -493,6 +493,14 @@ public class WorldConfiguration extends ConfigurationPart { + public Chunks chunks; + + public class Chunks extends ConfigurationPart { ++ ++ // Folia start - region threading - force prevent moving into unloaded chunks ++ @PostProcess ++ public void postProcess() { ++ this.preventMovingIntoUnloadedChunks = true; ++ } ++ // Folia end - region threading - force prevent moving into unloaded chunks ++ + public AutosavePeriod autoSaveInterval = AutosavePeriod.def(); + public int maxAutoSaveChunksPerTick = 24; + public int fixedChunkInhabitedTime = -1; +diff --git a/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java +index 41bf71d116ffc5431586ce54abba7f8def6c1dcf..1cf9a7677449ab8f03fb23d835e3fadce61542db 100644 +--- a/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java ++++ b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java +@@ -11,8 +11,16 @@ public class PaperSchoolableFish extends CraftFish implements SchoolableFish { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public AbstractSchoolingFish getHandleRaw() { ++ return (AbstractSchoolingFish)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractSchoolingFish getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractSchoolingFish) super.getHandle(); + } + +diff --git a/src/main/java/io/papermc/paper/entity/activation/ActivationType.java b/src/main/java/io/papermc/paper/entity/activation/ActivationType.java +index bed83c5897ff2ca52e97738832bd4cc08e36d762..5f8e59377bfe34f5abc9172ae24327dab3531e0a 100644 +--- a/src/main/java/io/papermc/paper/entity/activation/ActivationType.java ++++ b/src/main/java/io/papermc/paper/entity/activation/ActivationType.java +@@ -20,7 +20,7 @@ public enum ActivationType { + RAIDER, + MISC; + +- AABB boundingBox = new AABB(0, 0, 0, 0, 0, 0); ++ //AABB boundingBox = new AABB(0, 0, 0, 0, 0, 0); // Folia - threaded regions - replaced by local variable + + /** + * Returns the activation type for the given entity. +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java +index afe793c35f05a80058e80bcaee76ac45a40b04a2..9ddbb2d72e11c6abbbdb866f3010f276efceda41 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java +@@ -32,7 +32,9 @@ abstract class PaperPermissionManager implements PermissionManager { + @Override + @Nullable + public Permission getPermission(@NotNull String name) { ++ synchronized (this) { // Folia - synchronized + return this.permissions().get(name.toLowerCase(java.util.Locale.ENGLISH)); ++ } // Folia - synchronized + } + + @Override +@@ -52,12 +54,24 @@ abstract class PaperPermissionManager implements PermissionManager { + private void addPermission(@NotNull Permission perm, boolean dirty) { + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + ++ Boolean recalc; // Folia - synchronized ++ synchronized (this) { // Folia - synchronized + if (this.permissions().containsKey(name)) { + throw new IllegalArgumentException("The permission " + name + " is already defined!"); + } + + this.permissions().put(name, perm); +- this.calculatePermissionDefault(perm, dirty); ++ recalc = this.calculatePermissionDefault(perm, dirty); ++ } // Folia - synchronized ++ // Folia start - synchronize this class - we hold a lock now, prevent deadlock by moving this out ++ if (recalc != null) { ++ if (recalc.booleanValue()) { ++ this.dirtyPermissibles(true); ++ } else { ++ this.dirtyPermissibles(false); ++ } ++ } ++ // Folia end - synchronize this class - we hold a lock now, prevent deadlock by moving this out + } + + @Override +@@ -80,42 +94,58 @@ abstract class PaperPermissionManager implements PermissionManager { + + @Override + public void recalculatePermissionDefaults(@NotNull Permission perm) { ++ Boolean recalc = null; // Folia - synchronized ++ synchronized (this) { // Folia - synchronized + // we need a null check here because some plugins for some unknown reason pass null into this? + if (perm != null && this.permissions().containsKey(perm.getName().toLowerCase(Locale.ROOT))) { + this.defaultPerms().get(true).remove(perm); + this.defaultPerms().get(false).remove(perm); + +- this.calculatePermissionDefault(perm, true); ++ recalc = this.calculatePermissionDefault(perm, true); // Folia - synchronized ++ } ++ } // Folia - synchronized ++ // Folia start - synchronize this class - we hold a lock now, prevent deadlock by moving this out ++ if (recalc != null) { ++ if (recalc.booleanValue()) { ++ this.dirtyPermissibles(true); ++ } else { ++ this.dirtyPermissibles(false); ++ } + } ++ // Folia end - synchronize this class - we hold a lock now, prevent deadlock by moving this out + } + +- private void calculatePermissionDefault(@NotNull Permission perm, boolean dirty) { ++ private Boolean calculatePermissionDefault(@NotNull Permission perm, boolean dirty) { // Folia - synchronize this class + if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) { + this.defaultPerms().get(true).add(perm); + if (dirty) { +- this.dirtyPermissibles(true); ++ return Boolean.TRUE; // Folia - synchronize this class - we hold a lock now, prevent deadlock by moving this out + } + } + if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) { + this.defaultPerms().get(false).add(perm); + if (dirty) { +- this.dirtyPermissibles(false); ++ return Boolean.FALSE; // Folia - synchronize this class - we hold a lock now, prevent deadlock by moving this out + } + } ++ return null; // Folia - synchronize this class + } + + + @Override + public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) { ++ synchronized (this) { // Folia - synchronized + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = this.permSubs().computeIfAbsent(name, k -> new WeakHashMap<>()); + + map.put(permissible, true); ++ } // Folia - synchronized + } + + @Override + public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) { + String name = permission.toLowerCase(java.util.Locale.ENGLISH); ++ synchronized (this) { // Folia - synchronized + Map map = this.permSubs().get(name); + + if (map != null) { +@@ -125,11 +155,13 @@ abstract class PaperPermissionManager implements PermissionManager { + this.permSubs().remove(name); + } + } ++ } // Folia - synchronized + } + + @Override + @NotNull + public Set getPermissionSubscriptions(@NotNull String permission) { ++ synchronized (this) { // Folia - synchronized + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map map = this.permSubs().get(name); + +@@ -138,17 +170,21 @@ abstract class PaperPermissionManager implements PermissionManager { + } else { + return ImmutableSet.copyOf(map.keySet()); + } ++ } // Folia - synchronized + } + + @Override + public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) { ++ synchronized (this) { // Folia - synchronized + Map map = this.defSubs().computeIfAbsent(op, k -> new WeakHashMap<>()); + + map.put(permissible, true); ++ } // Folia - synchronized + } + + @Override + public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) { ++ synchronized (this) { // Folia - synchronized + Map map = this.defSubs().get(op); + + if (map != null) { +@@ -158,11 +194,13 @@ abstract class PaperPermissionManager implements PermissionManager { + this.defSubs().remove(op); + } + } ++ } // Folia - synchronized + } + + @Override + @NotNull + public Set getDefaultPermSubscriptions(boolean op) { ++ synchronized (this) { // Folia - synchronized + Map map = this.defSubs().get(op); + + if (map == null) { +@@ -170,19 +208,24 @@ abstract class PaperPermissionManager implements PermissionManager { + } else { + return ImmutableSet.copyOf(map.keySet()); + } ++ } // Folia - synchronized + } + + @Override + @NotNull + public Set getPermissions() { ++ synchronized (this) { // Folia - synchronized + return new HashSet<>(this.permissions().values()); ++ } // Folia - synchronized + } + + @Override + public void clearPermissions() { ++ synchronized (this) { // Folia - synchronized + this.permissions().clear(); + this.defaultPerms().get(true).clear(); + this.defaultPerms().get(false).clear(); ++ } // Folia - synchronized + } + + +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +index 3e82ea07ca4194844c5528446e2c4a46ff4acee5..adfd4c16809f6ddd9cc73e4bd845d7aed4925068 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +@@ -256,12 +256,7 @@ class PaperPluginInstanceManager { + + pluginName + " (Is it up to date?)", ex, plugin); // Paper + } + +- try { +- this.server.getScheduler().cancelTasks(plugin); +- } catch (Throwable ex) { +- this.handlePluginException("Error occurred (in the plugin loader) while cancelling tasks for " +- + pluginName + " (Is it up to date?)", ex, plugin); // Paper +- } ++ // Folia - region threading + + // Paper start - Folia schedulers + try { +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java +index d3b3a8baca013909fa9c6204d964d7d7efeb2719..fb7c6621e2805f4339c255f6c2e02c55ff4c502e 100644 +--- a/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java +@@ -64,6 +64,7 @@ public class PaperPluginMeta implements PluginMeta { + private PermissionConfiguration permissionConfiguration = new PermissionConfiguration(PermissionDefault.OP, List.of()); + @Required + private ApiVersion apiVersion; ++ private boolean foliaSupported = false; // Folia + + private Map> dependencies = new EnumMap<>(PluginDependencyLifeCycle.class); + +@@ -251,6 +252,13 @@ public class PaperPluginMeta implements PluginMeta { + return this.apiVersion.getVersionString(); + } + ++ // Folia start ++ @Override ++ public boolean isFoliaSupported() { ++ return this.foliaSupported; ++ } ++ // Folia end ++ + @Override + public @NotNull List getProvidedPlugins() { + return this.provides; +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java +index 0a27b468560ccf4b9588cd12d50c02e442f3024f..6369b13e1fcdbdb25dd9d6e4d3bffdedbee4f739 100644 +--- a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java +@@ -24,6 +24,11 @@ class PaperPluginProviderFactory implements PluginTypeFactory { ++ CraftServer.this.dispatchCommand(nmsEntity.getBukkitEntity(), commandLine); ++ }, ++ null, ++ 1L ++ ); ++ } else if (sender instanceof ConsoleCommandSender || sender instanceof io.papermc.paper.commands.FeedbackForwardingSender) { ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { ++ CraftServer.this.dispatchCommand(sender, commandLine); ++ }); ++ } else { ++ // huh? ++ throw new UnsupportedOperationException("Dispatching command for " + sender); ++ } ++ } ++ // Folia end - region threading ++ + @Override + public boolean dispatchCommand(CommandSender sender, String commandLine) { + Preconditions.checkArgument(sender != null, "sender cannot be null"); + Preconditions.checkArgument(commandLine != null, "commandLine cannot be null"); + org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + commandLine); // Spigot // Paper - Include command in error message + ++ // Folia start - region threading ++ if ((sender instanceof Entity entity)) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(((org.bukkit.craftbukkit.entity.CraftEntity)entity).getHandle(), "Dispatching command async"); ++ } else if (sender instanceof ConsoleCommandSender || sender instanceof net.minecraft.server.rcon.RconConsoleSource ++ || sender instanceof org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender ++ || sender instanceof io.papermc.paper.commands.FeedbackForwardingSender) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Dispatching command async"); ++ } else { ++ // huh? ++ throw new UnsupportedOperationException("Dispatching command for " + sender); ++ } ++ // Folia end - region threading ++ + if (this.commandMap.dispatch(sender, commandLine)) { + return true; + } +@@ -1285,6 +1322,7 @@ public final class CraftServer implements Server { + + @Override + public World createWorld(WorldCreator creator) { ++ if (true) throw new UnsupportedOperationException(); // Folia - not implemented properly yet + Preconditions.checkState(this.console.getAllLevels().iterator().hasNext(), "Cannot create additional worlds on STARTUP"); + //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot create a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. + Preconditions.checkArgument(creator != null, "WorldCreator cannot be null"); +@@ -1482,6 +1520,7 @@ public final class CraftServer implements Server { + + @Override + public boolean unloadWorld(World world, boolean save) { ++ if (true) throw new UnsupportedOperationException(); // Folia - not implemented properly yet + //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot unload a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. + if (world == null) { + return false; +@@ -3079,11 +3118,27 @@ public final class CraftServer implements Server { + + @Override + public double[] getTPS() { ++ // Folia start - region threading ++ ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool.SchedulableTick task = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentTickingTask(); ++ if (task == null) { ++ // might be on the shutdown thread, try retrieving the current region ++ if (io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion() != null) { ++ // we are on the shutdown thread ++ task = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion().getData().getRegionSchedulingHandle(); ++ } ++ } ++ if (!(task instanceof io.papermc.paper.threadedregions.TickRegionScheduler.RegionScheduleHandle tickHandle)) { ++ throw new UnsupportedOperationException("Not on any region"); ++ } ++ ++ // 1m, 5m, 15m ++ long currTime = System.nanoTime(); + return new double[] { +- net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), +- net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), +- net.minecraft.server.MinecraftServer.getServer().tps15.getAverage() ++ tickHandle.getTickReport1m(currTime).tpsData().segmentAll().average(), ++ tickHandle.getTickReport5m(currTime).tpsData().segmentAll().average(), ++ tickHandle.getTickReport15m(currTime).tpsData().segmentAll().average(), + }; ++ // Folia end - region threading + } + + // Paper start - adventure sounds +@@ -3254,7 +3309,7 @@ public final class CraftServer implements Server { + + @Override + public int getCurrentTick() { +- return net.minecraft.server.MinecraftServer.currentTick; ++ return (int)io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - region threading + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 1439d282167dc8a2e66f4896849153b810112988..82d26889661a944e057be0c450fb5a296122ea8e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -227,7 +227,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getTickableTileEntityCount() { +- return world.blockEntityTickers.size(); ++ throw new UnsupportedOperationException(); // Folia - region threading - TODO fix this? + } + + @Override +@@ -294,7 +294,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + // Paper start - per world spawn limits + for (SpawnCategory spawnCategory : SpawnCategory.values()) { + if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { +- setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); ++ this.spawnCategoryLimit.put(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); // Folia - region threading + } + } + // Paper end - per world spawn limits +@@ -365,6 +365,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public Chunk getChunkAt(int x, int z) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "Async chunk retrieval"); // Folia - region threading + warnUnsafeChunk("getting a faraway chunk", x, z); // Paper + net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); + return new CraftChunk(chunk); +@@ -395,10 +396,10 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean isChunkGenerated(int x, int z) { + // Paper start - Fix this method +- if (!Bukkit.isPrimaryThread()) { ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.getHandle(), x, z)) { // Folia - region threading + return java.util.concurrent.CompletableFuture.supplyAsync(() -> { + return CraftWorld.this.isChunkGenerated(x, z); +- }, world.getChunkSource().mainThreadProcessor).join(); ++ }, (run) -> { io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(this.getHandle(), x, z, run);}).join(); // Folia - region threading + } + ChunkAccess chunk = world.getChunkSource().getChunkAtImmediately(x, z); + if (chunk != null) { +@@ -455,7 +456,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + + private boolean unloadChunk0(int x, int z, boolean save) { +- org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // Folia - region threading + if (!this.isChunkLoaded(x, z)) { + return true; + } +@@ -472,6 +473,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean refreshChunk(int x, int z) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // Folia - region threading + ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); + if (playerChunk == null) return false; + +@@ -522,7 +524,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean loadChunk(int x, int z, boolean generate) { +- org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // Folia - region threading + warnUnsafeChunk("loading a faraway chunk", x, z); // Paper + ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper + +@@ -562,7 +564,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + final DistanceManager distanceManager = this.world.getChunkSource().chunkMap.distanceManager; + if (distanceManager.addPluginRegionTicket(new ChunkPos(x, z), plugin)) { +- this.getChunkAt(x, z); // ensure it's loaded ++ //this.getChunkAt(x, z); // ensure it's loaded // Folia - region threading - do not load chunks for tickets anymore to make this mt-safe + return true; + } + +@@ -616,21 +618,24 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean isChunkForceLoaded(int x, int z) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot read force-loaded chunk off global region"); // Folia - region threading + return this.getHandle().getForcedChunks().contains(ChunkPos.asLong(x, z)); + } + + @Override + public void setChunkForceLoaded(int x, int z, boolean forced) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify force-loaded chunks off global region"); // Folia - region threading + warnUnsafeChunk("forceloading a faraway chunk", x, z); // Paper + this.getHandle().setChunkForced(x, z, forced); + } + + @Override + public Collection getForceLoadedChunks() { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot read force-loaded chunks off global region"); // Folia - region threading + Set chunks = new HashSet<>(); + + for (long coord : this.getHandle().getForcedChunks()) { +- chunks.add(this.getChunkAt(ChunkPos.getX(coord), ChunkPos.getZ(coord))); ++ chunks.add(new org.bukkit.craftbukkit.CraftChunk(this.world, ChunkPos.getX(coord), ChunkPos.getZ(coord))); // Folia - region threading + } + + return Collections.unmodifiableCollection(chunks); +@@ -750,13 +755,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { +- this.world.captureTreeGeneration = true; +- this.world.captureBlockStates = true; ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // Folia - region threading ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - region threading ++ worldData.captureTreeGeneration = true; // Folia - region threading ++ worldData.captureBlockStates = true; // Folia - region threading + boolean grownTree = this.generateTree(loc, type); +- this.world.captureBlockStates = false; +- this.world.captureTreeGeneration = false; ++ worldData.captureBlockStates = false; // Folia - region threading ++ worldData.captureTreeGeneration = false; // Folia - region threading + if (grownTree) { // Copy block data to delegate +- for (BlockState blockstate : this.world.capturedBlockStates.values()) { ++ for (BlockState blockstate : worldData.capturedBlockStates.values()) { // Folia - region threading + BlockPos position = ((CraftBlockState) blockstate).getPosition(); + net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position); + int flag = ((CraftBlockState) blockstate).getFlag(); +@@ -764,10 +771,10 @@ public class CraftWorld extends CraftRegionAccessor implements World { + net.minecraft.world.level.block.state.BlockState newBlock = this.world.getBlockState(position); + this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flag, 512); + } +- this.world.capturedBlockStates.clear(); ++ worldData.capturedBlockStates.clear(); // Folia - region threading + return true; + } else { +- this.world.capturedBlockStates.clear(); ++ worldData.capturedBlockStates.clear(); // Folia - region threading + return false; + } + } +@@ -801,6 +808,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setTime(long time) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify time off of the global region"); // Folia - region threading + long margin = (time - this.getFullTime()) % 24000; + if (margin < 0) margin += 24000; + this.setFullTime(this.getFullTime() + margin); +@@ -813,6 +821,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setFullTime(long time) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify time off of the global region"); // Folia - region threading + // Notify anyone who's listening + TimeSkipEvent event = new TimeSkipEvent(this, TimeSkipEvent.SkipReason.CUSTOM, time - this.world.getDayTime()); + this.server.getPluginManager().callEvent(event); +@@ -840,7 +849,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public long getGameTime() { +- return this.world.levelData.getGameTime(); ++ return this.getHandle().getGameTime(); // Folia - region threading + } + + @Override +@@ -865,6 +874,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { + // Paper end - expand explosion API ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // Folia - region threading + net.minecraft.world.level.Level.ExplosionInteraction explosionType; + if (!breakBlocks) { + explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks +@@ -874,6 +884,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + explosionType = net.minecraft.world.level.Level.ExplosionInteraction.MOB; // Respect mobGriefing gamerule + } + ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // Folia - region threading + net.minecraft.world.entity.Entity entity = (source == null) ? null : ((CraftEntity) source).getHandle(); + return !this.world.explode0(entity, Explosion.getDefaultDamageSource(this.world, entity), null, x, y, z, power, setFire, explosionType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE, configurator).wasCanceled; // Paper - expand explosion API + } +@@ -956,6 +967,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // Folia - region threading + warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper + // Transient load for this tick + return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); +@@ -986,6 +998,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public void setBiome(int x, int y, int z, Holder bb) { + BlockPos pos = new BlockPos(x, 0, z); ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // Folia - region threading + if (this.world.hasChunkAt(pos)) { + net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); + +@@ -1316,6 +1329,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setStorm(boolean hasStorm) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading + this.world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents + this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) + this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) +@@ -1328,6 +1342,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setWeatherDuration(int duration) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading + this.world.serverLevelData.setRainTime(duration); + } + +@@ -1338,6 +1353,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setThundering(boolean thundering) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading + this.world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents + this.setThunderDuration(0); // Reset weather duration (legacy behaviour) + this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) +@@ -1350,6 +1366,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setThunderDuration(int duration) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading + this.world.serverLevelData.setThunderTime(duration); + } + +@@ -1360,6 +1377,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setClearWeatherDuration(int duration) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading + this.world.serverLevelData.setClearWeatherTime(duration); + } + +@@ -1558,6 +1576,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setKeepSpawnInMemory(boolean keepLoaded) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify keep spawn in memory off of the global region"); // Folia - region threading + if (keepLoaded) { + this.setGameRule(GameRule.SPAWN_CHUNK_RADIUS, this.getGameRuleDefault(GameRule.SPAWN_CHUNK_RADIUS)); + } else { +@@ -1626,6 +1645,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setHardcore(boolean hardcore) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.world.serverLevelData.settings.hardcore = hardcore; + } + +@@ -1638,6 +1658,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setTicksPerSpawns(SpawnCategory.ANIMAL, ticksPerAnimalSpawns); + } + +@@ -1650,6 +1671,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setTicksPerSpawns(SpawnCategory.MONSTER, ticksPerMonsterSpawns); + } + +@@ -1662,6 +1684,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setTicksPerWaterSpawns(int ticksPerWaterSpawns) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setTicksPerSpawns(SpawnCategory.WATER_ANIMAL, ticksPerWaterSpawns); + } + +@@ -1674,6 +1697,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setTicksPerWaterAmbientSpawns(int ticksPerWaterAmbientSpawns) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setTicksPerSpawns(SpawnCategory.WATER_AMBIENT, ticksPerWaterAmbientSpawns); + } + +@@ -1686,6 +1710,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setTicksPerWaterUndergroundCreatureSpawns(int ticksPerWaterUndergroundCreatureSpawns) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setTicksPerSpawns(SpawnCategory.WATER_UNDERGROUND_CREATURE, ticksPerWaterUndergroundCreatureSpawns); + } + +@@ -1698,11 +1723,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setTicksPerAmbientSpawns(int ticksPerAmbientSpawns) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setTicksPerSpawns(SpawnCategory.AMBIENT, ticksPerAmbientSpawns); + } + + @Override + public void setTicksPerSpawns(SpawnCategory spawnCategory, int ticksPerCategorySpawn) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); + Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); + +@@ -1719,21 +1746,25 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify metadata off of the global region"); // Folia - region threading + this.server.getWorldMetadata().setMetadata(this, metadataKey, newMetadataValue); + } + + @Override + public List getMetadata(String metadataKey) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot retrieve metadata off of the global region"); // Folia - region threading + return this.server.getWorldMetadata().getMetadata(this, metadataKey); + } + + @Override + public boolean hasMetadata(String metadataKey) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot retrieve metadata off of the global region"); // Folia - region threading + return this.server.getWorldMetadata().hasMetadata(this, metadataKey); + } + + @Override + public void removeMetadata(String metadataKey, Plugin owningPlugin) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify metadata off of the global region"); // Folia - region threading + this.server.getWorldMetadata().removeMetadata(this, metadataKey, owningPlugin); + } + +@@ -1746,6 +1777,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setMonsterSpawnLimit(int limit) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setSpawnLimit(SpawnCategory.MONSTER, limit); + } + +@@ -1758,6 +1790,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setAnimalSpawnLimit(int limit) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setSpawnLimit(SpawnCategory.ANIMAL, limit); + } + +@@ -1770,6 +1803,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setWaterAnimalSpawnLimit(int limit) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setSpawnLimit(SpawnCategory.WATER_ANIMAL, limit); + } + +@@ -1782,6 +1816,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setWaterAmbientSpawnLimit(int limit) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setSpawnLimit(SpawnCategory.WATER_AMBIENT, limit); + } + +@@ -1794,6 +1829,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setWaterUndergroundCreatureSpawnLimit(int limit) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setSpawnLimit(SpawnCategory.WATER_UNDERGROUND_CREATURE, limit); + } + +@@ -1806,6 +1842,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + @Deprecated + public void setAmbientSpawnLimit(int limit) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + this.setSpawnLimit(SpawnCategory.AMBIENT, limit); + } + +@@ -1828,6 +1865,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setSpawnLimit(SpawnCategory spawnCategory, int limit) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); + Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); + +@@ -1910,7 +1948,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + + ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); +- ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); ++ ChunkMap.TrackedEntity entityTracker = ((CraftEntity) entity).getHandle().moonrise$getTrackedEntity(); // Folia - region threading + if (entityTracker != null) { + entityTracker.broadcastAndSend(packet); + } +@@ -1931,7 +1969,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; + + ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(ResourceLocation.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); +- ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); ++ ChunkMap.TrackedEntity entityTracker = ((CraftEntity)entity).getHandle().moonrise$getTrackedEntity(); // Folia - region threading + if (entityTracker != null) { + entityTracker.broadcastAndSend(packet); + } +@@ -2014,6 +2052,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean setGameRuleValue(String rule, String value) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + // No null values allowed + if (rule == null || value == null) return false; + +@@ -2062,6 +2101,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean setGameRule(GameRule rule, T newValue) { ++ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading + Preconditions.checkArgument(rule != null, "GameRule cannot be null"); + Preconditions.checkArgument(newValue != null, "GameRule value cannot be null"); + +@@ -2289,6 +2329,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { ++ // Folia start - region threading ++ if (sourceEntity != null && !Bukkit.isOwnedByCurrentRegion(sourceEntity)) { ++ throw new IllegalStateException("Cannot send game event asynchronously"); ++ } ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); ++ // Folia end - region threading + getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); + } + // Paper end +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 5cb69d0b822e11a99a96aef4f59986d083b079f4..a2f35f6d057b098a016a40094d84c54cb5e174fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -75,6 +75,11 @@ public class CraftBlock implements Block { + } + + public net.minecraft.world.level.block.state.BlockState getNMS() { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + return this.world.getBlockState(this.position); + } + +@@ -157,6 +162,11 @@ public class CraftBlock implements Block { + } + + private void setData(final byte data, int flag) { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); + } + +@@ -198,6 +208,11 @@ public class CraftBlock implements Block { + } + + public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { ++ // Folia start - region threading ++ if (world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // Folia end - region threading + // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup + if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes + // SPIGOT-4612: faster - just clear tile +@@ -343,18 +358,33 @@ public class CraftBlock implements Block { + + @Override + public Biome getBiome() { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); + } + + // Paper start + @Override + public Biome getComputedBiome() { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); + } + // Paper end + + @Override + public void setBiome(Biome bio) { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); + } + +@@ -402,6 +432,11 @@ public class CraftBlock implements Block { + + @Override + public boolean isBlockFaceIndirectlyPowered(BlockFace face) { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); + + Block relative = this.getRelative(face); +@@ -414,6 +449,11 @@ public class CraftBlock implements Block { + + @Override + public int getBlockPower(BlockFace face) { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + int power = 0; + net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); + int x = this.getX(); +@@ -500,6 +540,11 @@ public class CraftBlock implements Block { + + @Override + public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + // Paper end + // Order matters here, need to drop before setting to air so skulls can get their data + net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); +@@ -543,21 +588,27 @@ public class CraftBlock implements Block { + + @Override + public boolean applyBoneMeal(BlockFace face) { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + Direction direction = CraftBlock.blockFaceToNotch(face); + BlockFertilizeEvent event = null; + ServerLevel world = this.getCraftWorld().getHandle(); + UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false)); + ++ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - region threading + // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent +- world.captureTreeGeneration = true; ++ worldData.captureTreeGeneration = true; // Folia - region threading + InteractionResult result = BoneMealItem.applyBonemeal(context); +- world.captureTreeGeneration = false; ++ worldData.captureTreeGeneration = false; // Folia - region threading + +- if (world.capturedBlockStates.size() > 0) { +- TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; +- List blocks = new ArrayList<>(world.capturedBlockStates.values()); +- world.capturedBlockStates.clear(); ++ if (worldData.capturedBlockStates.size() > 0) { // Folia - region threading ++ TreeType treeType = SaplingBlock.treeTypeRT.get(); // Folia - region threading ++ SaplingBlock.treeTypeRT.set(null); // Folia - region threading ++ List blocks = new ArrayList<>(worldData.capturedBlockStates.values()); // Folia - region threading ++ worldData.capturedBlockStates.clear(); // Folia - region threading + StructureGrowEvent structureEvent = null; + + if (treeType != null) { +@@ -644,6 +695,11 @@ public class CraftBlock implements Block { + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + Preconditions.checkArgument(start != null, "Location start cannot be null"); + Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); + start.checkFinite(); +@@ -685,6 +741,11 @@ public class CraftBlock implements Block { + + @Override + public boolean canPlace(BlockData data) { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + Preconditions.checkArgument(data != null, "BlockData cannot be null"); + net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); + net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); +@@ -719,18 +780,32 @@ public class CraftBlock implements Block { + + @Override + public void tick() { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + final ServerLevel level = this.world.getMinecraftWorld(); + this.getNMS().tick(level, this.position, level.random); + } + +- + @Override + public void fluidTick() { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + this.getNMSFluid().tick(this.world.getMinecraftWorld(), this.position, this.getNMS()); + } + + @Override + public void randomTick() { ++ // Folia start - region threading ++ if (this.world instanceof ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); ++ } ++ // Folia end - region threading + final ServerLevel level = this.world.getMinecraftWorld(); + this.getNMS().randomTick(level, this.position, level.random); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 768d3f93da2522d467183654260a8bd8653588b1..7a6b24f10debaaf6426c752958886f78f3bfe573 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -25,7 +25,7 @@ public abstract class CraftBlockEntityState extends Craft + private final T tileEntity; + private final T snapshot; + public boolean snapshotDisabled; // Paper +- public static boolean DISABLE_SNAPSHOT = false; // Paper ++ public static final ThreadLocal DISABLE_SNAPSHOT = ThreadLocal.withInitial(() -> Boolean.FALSE); // Paper // Folia - region threading + + public CraftBlockEntityState(World world, T tileEntity) { + super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); +@@ -34,8 +34,8 @@ public abstract class CraftBlockEntityState extends Craft + + try { // Paper - Show blockstate location if we failed to read it + // Paper start +- this.snapshotDisabled = DISABLE_SNAPSHOT; +- if (DISABLE_SNAPSHOT) { ++ this.snapshotDisabled = DISABLE_SNAPSHOT.get().booleanValue(); // Folia - region threading ++ if (this.snapshotDisabled) { // Folia - region threading + this.snapshot = this.tileEntity; + } else { + this.snapshot = this.createSnapshot(tileEntity); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..def7749e6dc4ae8351b72deefc75936629c33d7f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -215,6 +215,12 @@ public class CraftBlockState implements BlockState { + LevelAccessor access = this.getWorldHandle(); + CraftBlock block = this.getBlock(); + ++ // Folia start - region threading ++ if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ } ++ // Folia end - region threading ++ + if (block.getType() != this.getType()) { + if (!force) { + return false; +@@ -350,6 +356,9 @@ public class CraftBlockState implements BlockState { + + @Override + public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { ++ // Folia start - region threading ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); ++ // Folia end - region threading + this.requirePlaced(); + net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item); + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..69e8a170a80c2fde79bc015cd54879896c110d9d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +@@ -249,8 +249,8 @@ public final class CraftBlockStates { + net.minecraft.world.level.block.state.BlockState blockData = craftBlock.getNMS(); + BlockEntity tileEntity = craftBlock.getHandle().getBlockEntity(blockPosition); + // Paper start - block state snapshots +- boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT; +- CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot; ++ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT.get().booleanValue(); // Folia - region threading ++ CraftBlockEntityState.DISABLE_SNAPSHOT.set(Boolean.valueOf(!useSnapshot)); // Folia - region threading + try { + // Paper end + CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockPosition, blockData, tileEntity); +@@ -258,7 +258,7 @@ public final class CraftBlockStates { + return blockState; + // Paper start + } finally { +- CraftBlockEntityState.DISABLE_SNAPSHOT = prev; ++ CraftBlockEntityState.DISABLE_SNAPSHOT.set(Boolean.valueOf(prev)); // Folia - region threading + } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index a45e658996e483e9a21cfd8178153ddb7b87ae69..25303f144422469350fdc6f84320b16bcc9f6e0c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +@@ -50,7 +50,7 @@ public class ConsoleCommandCompleter implements Completer { + return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of(); + } + }; +- server.getServer().processQueue.add(syncCompletions); ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(syncCompletions); // Folia - region threading + try { + final List legacyCompletions = syncCompletions.get(); + completions.removeIf(it -> !legacyCompletions.contains(it.suggestion())); // remove any suggestions that were removed +@@ -98,7 +98,7 @@ public class ConsoleCommandCompleter implements Completer { + return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); + } + }; +- server.getServer().processQueue.add(waitable); // Paper - Remove "this." ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(waitable); // Folia - region threading + try { + List offers = waitable.get(); + if (offers == null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +index e8d82054d17ef1859eb57f3871043b3fe3de22b9..6fae4697512e6e1ded15938d4cdce93e7e2eef39 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit.entity; + ++import net.minecraft.world.entity.Entity; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Projectile; + +@@ -38,6 +39,13 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti + this.getHandle().hasBeenShot = beenShot; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.Projectile getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.Projectile)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public boolean canHitEntity(org.bukkit.entity.Entity entity) { + return this.getHandle().canHitEntityPublic(((CraftEntity) entity).getHandle()); +@@ -55,6 +63,7 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti + + @Override + public net.minecraft.world.entity.projectile.Projectile getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.Projectile) entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java +index e7fe8afd1171783ccef891c59413c57d09493509..f9a0b1ffa9fd356b7764ae78cde0c6d1dad3f75f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java +@@ -142,6 +142,7 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr + + @Override + public net.minecraft.world.entity.projectile.AbstractArrow getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.AbstractArrow) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +index 467693a60786688b753cebac3b0a88898e332eee..5c6bd9186e47d1414c5e7bd4fa46a8e305390908 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +@@ -17,8 +17,16 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.horse.AbstractHorse getHandleRaw() { ++ return (net.minecraft.world.entity.animal.horse.AbstractHorse)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.horse.AbstractHorse getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.horse.AbstractHorse) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java +index 5beaa2bb0d58fe477ce8d2de8b77600d3b416d8c..c8406f2d83f4c8b60efec0de546f45760c759a2a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java +@@ -15,8 +15,17 @@ public abstract class CraftAbstractSkeleton extends CraftMonster implements Abst + throw new UnsupportedOperationException("Not supported."); + } + // Paper start ++ ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.AbstractSkeleton getHandleRaw() { ++ return (net.minecraft.world.entity.monster.AbstractSkeleton)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.AbstractSkeleton getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.AbstractSkeleton) super.getHandle(); + } + // Paper end +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +index 3199f04d00836a0a51547c679f3f3c80d00da502..a1959919109fe04d4b829dcd2d244842ab05fe13 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +@@ -15,8 +15,16 @@ public class CraftAbstractVillager extends CraftAgeable implements CraftMerchant + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.npc.AbstractVillager getHandleRaw() { ++ return (net.minecraft.world.entity.npc.AbstractVillager)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.npc.AbstractVillager getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Villager) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java +index 59df9031e8b4466c8687671d745318e7ee83d271..b91b11c2e1ed5df27e6ff99eb5cc25b931e0b79d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java +@@ -17,6 +17,7 @@ public abstract class CraftAbstractWindCharge extends CraftFireball implements A + + @Override + public net.minecraft.world.entity.projectile.windcharge.AbstractWindCharge getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.windcharge.AbstractWindCharge) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java +index ae16e8d1bfe8e9315391510eddb367a3fbdc9e03..2b165c209f65de06f55ed51817e33b92463a2987 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java +@@ -63,8 +63,16 @@ public class CraftAgeable extends CraftCreature implements Ageable { + } + } + ++ // Folia start - region threading ++ @Override ++ public AgeableMob getHandleRaw() { ++ return (AgeableMob)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AgeableMob getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AgeableMob) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java +index c64918175ec08d20cde2bda9e0cac8b474385fe0..0df0824d56d62f7b82fcca8f0b9a6175f012e8d3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java +@@ -16,8 +16,16 @@ public class CraftAllay extends CraftCreature implements org.bukkit.entity.Allay + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public Allay getHandleRaw() { ++ return (Allay)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Allay getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Allay) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java +index 2a2f9f0907eafcabef26a41d20f64a0aa953d181..9d56293083aac5c14e8333366fd4cf6148486585 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java +@@ -9,8 +9,16 @@ public class CraftAmbient extends CraftMob implements Ambient { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public AmbientCreature getHandleRaw() { ++ return (AmbientCreature)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AmbientCreature getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AmbientCreature) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java +index ab42bc721d5b6c17c2ca6c7153b757571aea05e8..e48528689d49c01aa2b0c1599c66f3c1e94c9cd6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java +@@ -15,8 +15,16 @@ public class CraftAnimals extends CraftAgeable implements Animals { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public Animal getHandleRaw() { ++ return (Animal)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Animal getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Animal) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +index f9c113dc018702159345240d6d0de85767afa0c3..0872943dc4e5895728d12289cb23682c9bef290c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +@@ -28,8 +28,16 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.AreaEffectCloud getHandleRaw() { ++ return (net.minecraft.world.entity.AreaEffectCloud)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.AreaEffectCloud getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.AreaEffectCloud) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java +index e7f2d8de25a489d7f52c78c750e6f7f9b8fee177..75191dd32bba12b5742702a2af151b1079a6b48f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java +@@ -11,6 +11,7 @@ public class CraftArmadillo extends CraftAnimals implements Armadillo { + + @Override + public net.minecraft.world.entity.animal.armadillo.Armadillo getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.armadillo.Armadillo) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +index 184fe8257e5ffb0ef090ffa2833786a4db8b59ea..6c5358f77be3e46860b0c3c49d36b25286af6851 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java +@@ -20,8 +20,16 @@ public class CraftArmorStand extends CraftLivingEntity implements ArmorStand { + return "CraftArmorStand"; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.decoration.ArmorStand getHandleRaw() { ++ return (net.minecraft.world.entity.decoration.ArmorStand)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.decoration.ArmorStand getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.decoration.ArmorStand) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +index 5661d72e70c082672e22f3ddbd67b88eb57a3c18..f814660be50fdfbf3866bd5d59a62ea4ce0d5c6c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +@@ -26,6 +26,7 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow { + + @Override + public net.minecraft.world.entity.projectile.Arrow getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.Arrow) this.entity; + } + +@@ -90,6 +91,13 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow { + return true; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.AbstractArrow getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.AbstractArrow)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public void setBasePotionData(PotionData data) { + this.setBasePotionType(CraftPotionUtil.fromBukkit(data)); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java +index cbfca242f820d238b112f8ce64e9de8398c48a1c..efbfc8480bddf901fe0acebc06408ee625b57418 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java +@@ -10,8 +10,16 @@ public class CraftAxolotl extends CraftAnimals implements Axolotl, io.papermc.pa + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.axolotl.Axolotl getHandleRaw() { ++ return (net.minecraft.world.entity.animal.axolotl.Axolotl)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.axolotl.Axolotl getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.axolotl.Axolotl) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java +index 1bb72f28085f3885bec068b586ec222111044884..cb56b6690a385e76197cfc0667ebdec72f0cd096 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java +@@ -8,8 +8,16 @@ public class CraftBat extends CraftAmbient implements Bat { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.ambient.Bat getHandleRaw() { ++ return (net.minecraft.world.entity.ambient.Bat)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.ambient.Bat getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.ambient.Bat) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java +index 3dac93b0ab5d5acf5b33dc4b0efed60319eb657b..6ade6ca1a32f824271b7deeabc4dd154ae5a67b6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java +@@ -13,8 +13,16 @@ public class CraftBee extends CraftAnimals implements Bee { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Bee getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Bee)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Bee getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Bee) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java +index a4c9c73691300880777483b0beb17e1bd6779d06..05951297aaed63c22f038703ad6fb68dfcec5227 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java +@@ -8,8 +8,16 @@ public class CraftBlaze extends CraftMonster implements Blaze { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Blaze getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Blaze)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Blaze getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Blaze) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java +index 5b0dd9aae3fbd9257d0375a37a07c812199d64a2..d22538ecda7685093f400ee560ae53c206ed62b2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java +@@ -8,8 +8,16 @@ public class CraftBlockAttachedEntity extends CraftEntity { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public BlockAttachedEntity getHandleRaw() { ++ return (BlockAttachedEntity)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public BlockAttachedEntity getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (BlockAttachedEntity) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java +index dd91de8f24c27b9318c2a898a49991d74c100bff..b951571eda47da97ee73ba7d9b71b4f6cf0373d6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java +@@ -12,8 +12,16 @@ public class CraftBlockDisplay extends CraftDisplay implements BlockDisplay { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.Display.BlockDisplay getHandleRaw() { ++ return (net.minecraft.world.entity.Display.BlockDisplay)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.Display.BlockDisplay getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.Display.BlockDisplay) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +index 2a2839c31989d127739d829159a8b6e5b9a5210b..fb87800c02d5ff9bcb197170c11e305273cea083 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +@@ -101,8 +101,16 @@ public abstract class CraftBoat extends CraftVehicle implements Boat, io.papermc + return CraftBoat.boatStatusFromNms(this.getHandle().status); + } + ++ // Folia start - region threading ++ @Override ++ public AbstractBoat getHandleRaw() { ++ return (AbstractBoat)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractBoat getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractBoat) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java +index e8e4704304504e69c7964dcd4df8ce5db9e92bf6..20630858d00fa23e911ec38788df971a12f98c6a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java +@@ -12,6 +12,7 @@ public class CraftBogged extends CraftAbstractSkeleton implements Bogged, io.pap + + @Override + public net.minecraft.world.entity.monster.Bogged getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Bogged) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java +index 7648e2c700a55f9c0b3539dc720903238d138d54..b21f1654ddd2a4d7c85baae44fef10842905fbf9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java +@@ -13,6 +13,7 @@ public class CraftBreeze extends CraftMonster implements Breeze { + + @Override + public net.minecraft.world.entity.monster.breeze.Breeze getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.breeze.Breeze) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java +index e88e52a9b8a4d2d750101b0529cbe2a9976e91dd..0eadb421cc505c4639f68c932d284e8ef56f7f57 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java +@@ -10,6 +10,7 @@ public class CraftBreezeWindCharge extends CraftAbstractWindCharge implements Br + + @Override + public net.minecraft.world.entity.projectile.windcharge.BreezeWindCharge getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.windcharge.BreezeWindCharge) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java +index 80e571c977db5cdf43bfbfce035f37a3fa325c95..562ac40645f98452d0d923146d4e95c59b029f5b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java +@@ -11,8 +11,16 @@ public class CraftCamel extends CraftAbstractHorse implements Camel { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.camel.Camel getHandleRaw() { ++ return (net.minecraft.world.entity.animal.camel.Camel)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.camel.Camel getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.camel.Camel) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +index 1a09082fcde81e3834c98903bda47aef90391870..ab36b4283e3fbccca491ff3ba93ce4d9d89c1ba8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +@@ -18,8 +18,16 @@ public class CraftCat extends CraftTameableAnimal implements Cat { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Cat getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Cat)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Cat getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Cat) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java +index 4f661fbdb860cf550da0d952b775fe6f990b43b3..2dfbfbbe98815a303516d88e6ea96b9fba9b7f39 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java +@@ -8,8 +8,16 @@ public class CraftCaveSpider extends CraftSpider implements CaveSpider { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.CaveSpider getHandleRaw() { ++ return (net.minecraft.world.entity.monster.CaveSpider)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.CaveSpider getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.CaveSpider) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java +index a1e04bb965f18ffd07e2f5bf827c5e4ddd6aeeda..8ba8189ddff9f35a60c31015cccf6480246cf21c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java +@@ -15,8 +15,16 @@ public abstract class CraftChestBoat extends CraftBoat implements org.bukkit.ent + this.inventory = new CraftInventory(entity); + } + ++ // Folia start - region threading ++ @Override ++ public AbstractChestBoat getHandleRaw() { ++ return (AbstractChestBoat)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractChestBoat getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractChestBoat) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java +index 40ee96e31dea64ab3a77553dbb6daad001736f2e..9cdb7e5ce6883709b709e88037e70a1953d755a0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java +@@ -10,8 +10,16 @@ public abstract class CraftChestedHorse extends CraftAbstractHorse implements Ch + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public AbstractChestedHorse getHandleRaw() { ++ return (AbstractChestedHorse)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractChestedHorse getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractChestedHorse) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java +index 96f6e2fd9c6b20d34122abfe5c7fba732502d5a0..2546ce4d7a25bfe6be1533bfbc770726815e8148 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java +@@ -9,8 +9,16 @@ public class CraftChicken extends CraftAnimals implements Chicken { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Chicken getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Chicken)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Chicken getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Chicken) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java +index 63e6b07e3b159c74d9ef17be20b5ab43d07f0f5f..44fa01798eed8368fa0187cecb88de830d7d2e16 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java +@@ -9,8 +9,16 @@ public class CraftCod extends io.papermc.paper.entity.PaperSchoolableFish implem + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Cod getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Cod)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Cod getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Cod) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java +index c2583982d84c736639eec511daba594d7806a628..d31bba789c51bc344d21a357f54dd8ef55b88873 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java +@@ -32,8 +32,16 @@ public class CraftComplexPart extends CraftEntity implements ComplexEntityPart { + return this.getParent().isValid(); + } + ++ // Folia start - region threading ++ @Override ++ public EnderDragonPart getHandleRaw() { ++ return (EnderDragonPart)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public EnderDragonPart getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (EnderDragonPart) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java +index 7babc404e4920cd264206d4e83b1be6f841cdb8c..7a5312ab0fe3a21907a1d6b82fab9b4dce15c44e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java +@@ -9,8 +9,16 @@ public class CraftCow extends CraftAnimals implements Cow { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Cow getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Cow)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Cow getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Cow) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java +index 267f3c85058ef7c73e372c04493cfa6c907e44bb..df838d551fa08895e390eb793506e2f3697555f4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java +@@ -9,8 +9,16 @@ public class CraftCreaking extends CraftMonster implements org.bukkit.entity.Cre + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public Creaking getHandleRaw() { ++ return (Creaking)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Creaking getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Creaking) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java +index 664d9c1793b823ed03f198a936f2ebd9b7695898..6cbe6b6438296b6137ceea01b21ab6a69da2cc9c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java +@@ -9,8 +9,16 @@ public class CraftCreature extends CraftMob implements Creature { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public PathfinderMob getHandleRaw() { ++ return (PathfinderMob)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public PathfinderMob getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (PathfinderMob) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java +index 42dd26b9170f7d217d73f725a6b8440b45ac2190..e59a29ee70e8b1f525c370bb711fa77a5732c500 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java +@@ -87,6 +87,13 @@ public class CraftCreeper extends CraftMonster implements Creeper { + this.getHandle().ignite(); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Creeper getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Creeper)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Entity getIgniter() { + return (this.getHandle().entityIgniter != null) ? this.getHandle().entityIgniter.getBukkitEntity() : null; +@@ -94,6 +101,7 @@ public class CraftCreeper extends CraftMonster implements Creeper { + + @Override + public net.minecraft.world.entity.monster.Creeper getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Creeper) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java +index 48eeb1d9ba0ad6f895bfe507a6fbe4b9c9530e47..65301b94dc8d813c487deff24cd04b379e666e98 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java +@@ -12,8 +12,16 @@ public class CraftDisplay extends CraftEntity implements Display { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.Display getHandleRaw() { ++ return (net.minecraft.world.entity.Display)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.Display getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.Display) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +index 83867b9c5497e6e793b21c482646cc419587e182..55dfb073e4355e68855580f26464af6cf1c6ac33 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +@@ -9,8 +9,16 @@ public class CraftDolphin extends CraftAgeable implements Dolphin { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Dolphin getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Dolphin)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Dolphin getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Dolphin) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java +index 51fc4acae9f20e8891069704e4a27f212b870766..2b27d3e685ee1882dc6ecc1ceaee2fb52f1b548f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java +@@ -9,8 +9,16 @@ public class CraftDrowned extends CraftZombie implements Drowned, com.destroysto + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Drowned getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Drowned)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Drowned getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Drowned) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java +index 010e9e922a6e30df4e40da151cfd398d1062633e..8f36a715a5fdf1595cdfdad3d9971cca39279777 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java +@@ -9,8 +9,16 @@ public class CraftEgg extends CraftThrowableProjectile implements Egg { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public ThrownEgg getHandleRaw() { ++ return (ThrownEgg)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public ThrownEgg getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (ThrownEgg) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java +index 676dd5331bec75407a74aea2a89e78ab72d69724..4f876511b116dd6e7704f1f047af6fab2c3a3e47 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java +@@ -39,8 +39,16 @@ public class CraftEnderCrystal extends CraftEntity implements EnderCrystal { + } + } + ++ // Folia start - region threading ++ @Override ++ public EndCrystal getHandleRaw() { ++ return (EndCrystal)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public EndCrystal getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (EndCrystal) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java +index 1ef0ec7ed3b13c25d76c03c7013c8e2eaba4d66a..9f37334ba0e2358f583df5a6d3e347909cfe681e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java +@@ -30,8 +30,16 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon, CraftEnem + return builder.build(); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.boss.enderdragon.EnderDragon getHandleRaw() { ++ return (net.minecraft.world.entity.boss.enderdragon.EnderDragon)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.boss.enderdragon.EnderDragon getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.boss.enderdragon.EnderDragon) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java +index 33ae03b78b01c005a291a343b42507fb539e81a6..36aec95539044edd429c17833338638262b9db00 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java +@@ -16,8 +16,16 @@ public class CraftEnderDragonPart extends CraftComplexPart implements EnderDrago + return (EnderDragon) super.getParent(); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.boss.EnderDragonPart getHandleRaw() { ++ return (net.minecraft.world.entity.boss.EnderDragonPart)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.boss.EnderDragonPart getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.boss.EnderDragonPart) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java +index 3bb8d74f2b59c7f0c7c1cbde47a570d628ceceb2..25d7577d17d52dc00a355a684f1493efb2e88584 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java +@@ -9,8 +9,16 @@ public class CraftEnderPearl extends CraftThrowableProjectile implements EnderPe + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public ThrownEnderpearl getHandleRaw() { ++ return (ThrownEnderpearl)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public ThrownEnderpearl getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (ThrownEnderpearl) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java +index 27f56fa4b7ef92a9a4dfa6b782350424b88210f2..e76390fe22e2e846313c9a5b2c7f5492f798ca3e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java +@@ -15,8 +15,16 @@ public class CraftEnderSignal extends CraftEntity implements EnderSignal { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public EyeOfEnder getHandleRaw() { ++ return (EyeOfEnder)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public EyeOfEnder getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (EyeOfEnder) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +index 983b9d6ddb58eff297e96e5c8b28ec427efa267d..16e33e302f8a60f1f9ff67929dc7c63cd5192a37 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +@@ -62,8 +62,16 @@ public class CraftEnderman extends CraftMonster implements Enderman { + } + // Paper end + ++ // Folia start - region threading ++ @Override ++ public EnderMan getHandleRaw() { ++ return (EnderMan)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public EnderMan getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (EnderMan) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java +index d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8..399ef60ab5f1bf02b638c8c46a72d297932f6b38 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java +@@ -9,8 +9,16 @@ public class CraftEndermite extends CraftMonster implements Endermite { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Endermite getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Endermite)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Endermite getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Endermite) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index f8762064e0f377740688932c62145f7c789ea7ed..6919fbc5c432d510d965bb3fb2ea7537c87b3ea6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -83,6 +83,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.apiScheduler; + }; + // Paper end - Folia schedulers ++ // Folia start - region threading ++ public boolean isPurged() { ++ return this.taskScheduler.isRetired(); ++ } ++ // Folia end - region threading + + public CraftEntity(final CraftServer server, final Entity entity) { + this.server = server; +@@ -240,6 +245,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + + @Override + public boolean teleport(Location location, TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { ++ // Folia start - region threading ++ if (true) { ++ throw new UnsupportedOperationException("Must use teleportAsync while in region threading"); ++ } ++ // Folia end - region threading + // Paper end + Preconditions.checkArgument(location != null, "location cannot be null"); + location.checkFinite(); +@@ -529,6 +539,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + public Entity getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return this.entity; + } + +@@ -722,7 +733,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + ImmutableSet.Builder players = ImmutableSet.builder(); + + ServerLevel world = ((CraftWorld) this.getWorld()).getHandle(); +- ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId()); ++ ChunkMap.TrackedEntity entityTracker = this.getHandle().moonrise$getTrackedEntity(); // Folia - region threading + + if (entityTracker != null) { + for (ServerPlayerConnection connection : entityTracker.seenBy) { +@@ -1026,7 +1037,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + ServerLevel world = ((CraftWorld) this.getWorld()).getHandle(); +- ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId()); ++ ChunkMap.TrackedEntity entityTracker = this.getHandle().moonrise$getTrackedEntity(); // Folia - region threading + + if (entityTracker == null) { + return; +@@ -1045,7 +1056,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + ServerLevel world = ((CraftWorld) this.getWorld()).getHandle(); +- ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId()); ++ ChunkMap.TrackedEntity entityTracker = this.entity.moonrise$getTrackedEntity(); // Folia - region threading + + if (entityTracker == null) { + return; +@@ -1079,29 +1090,43 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + location.checkFinite(); + Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call. + +- net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle(); ++ // Folia start - region threading + java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); +- +- world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), +- this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL, (list) -> { +- net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { +- final net.minecraft.server.level.ServerChunkCache chunkCache = world.getChunkSource(); +- for (final net.minecraft.world.level.chunk.ChunkAccess chunk : list) { +- chunkCache.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); +- } +- try { +- ret.complete(CraftEntity.this.teleport(locationClone, cause, teleportFlags) ? Boolean.TRUE : Boolean.FALSE); +- } catch (Throwable throwable) { +- if (throwable instanceof ThreadDeath) { +- throw (ThreadDeath)throwable; +- } +- net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable); +- ret.completeExceptionally(throwable); +- } +- }); +- }); ++ java.util.function.Consumer run = (Entity nmsEntity) -> { ++ boolean success = nmsEntity.teleportAsync( ++ ((CraftWorld)locationClone.getWorld()).getHandle(), ++ new net.minecraft.world.phys.Vec3(locationClone.getX(), locationClone.getY(), locationClone.getZ()), ++ locationClone.getYaw(), locationClone.getPitch(), net.minecraft.world.phys.Vec3.ZERO, ++ cause == null ? TeleportCause.UNKNOWN : cause, ++ Entity.TELEPORT_FLAG_LOAD_CHUNK | Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS, // preserve behavior with old API: dismount the entity so it can teleport ++ (Entity entityTp) -> { ++ ret.complete(Boolean.TRUE); ++ } ++ ); ++ if (!success) { ++ ret.complete(Boolean.FALSE); ++ } ++ }; ++ if (org.bukkit.Bukkit.isOwnedByCurrentRegion(this)) { ++ run.accept(this.getHandle()); ++ return ret; ++ } ++ boolean scheduled = this.taskScheduler.schedule( ++ // success ++ run, ++ // retired ++ (Entity nmsEntity) -> { ++ ret.complete(Boolean.FALSE); ++ }, ++ 1L ++ ); ++ ++ if (!scheduled) { ++ ret.complete(Boolean.FALSE); ++ } + + return ret; ++ // Folia end - region threading + } + // Paper end - more teleport API / async chunk API + +@@ -1214,8 +1239,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + // Paper start - tracked players API + @Override + public Set getTrackedPlayers() { +- ServerLevel world = (net.minecraft.server.level.ServerLevel)this.entity.level(); +- ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.entity.getId()); ++ ChunkMap.TrackedEntity tracker = this.entity.moonrise$getTrackedEntity(); // Folia - region threading + if (tracker == null) { + return java.util.Collections.emptySet(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java +index 3a890cccf1766758794f3a3b5d31428f42590049..8c148db1b84c65b89fb2779e5b96a71ea4900083 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java +@@ -11,8 +11,16 @@ public class CraftEvoker extends CraftSpellcaster implements Evoker { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Evoker getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Evoker)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Evoker getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Evoker) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java +index 19b368cc862cd7e3e1f0e89401a7d099e3eaefa3..4a1c1af06719ff75f6ec2ac27198858b549b0302 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java +@@ -11,8 +11,16 @@ public class CraftEvokerFangs extends CraftEntity implements EvokerFangs { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.EvokerFangs getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.EvokerFangs)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.EvokerFangs getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.EvokerFangs) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +index 650e4a01cecc4cc08e7ff9ebcc4c367084351f21..81b2b850dd7d08f2fae7baf56733d753b68d294c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java +@@ -42,8 +42,16 @@ public class CraftExperienceOrb extends CraftEntity implements ExperienceOrb { + } + // Paper end + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.ExperienceOrb getHandleRaw() { ++ return (net.minecraft.world.entity.ExperienceOrb)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.ExperienceOrb getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.ExperienceOrb) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +index 1359d25a32b4a5d5e8e68ce737bd19f7b5afaf69..4c6ac7f2531311d24081b397c60b2f8b183fad34 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +@@ -14,8 +14,16 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public FallingBlockEntity getHandleRaw() { ++ return (FallingBlockEntity)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public FallingBlockEntity getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (FallingBlockEntity) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java +index 43d7bea201a52cfeacf60c75caa28dfd2c4ff164..ac7237e8c28377d5f9abf38b628215ac865c9709 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java +@@ -83,8 +83,16 @@ public class CraftFireball extends AbstractProjectile implements Fireball { + } + // Paper end - Expose power on fireball projectiles + ++ // Folia start - region threading ++ @Override ++ public AbstractHurtingProjectile getHandleRaw() { ++ return (AbstractHurtingProjectile)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractHurtingProjectile getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractHurtingProjectile) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +index 759b6e54db93792c9862b1f1625118ac6fa49d7a..6fdd39c78a2f7c1c53d5de16e09e0f271c42039e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +@@ -37,8 +37,16 @@ public class CraftFirework extends CraftProjectile implements Firework { + // Paper end - Expose firework item directly + } + ++ // Folia start - region threading ++ @Override ++ public FireworkRocketEntity getHandleRaw() { ++ return (FireworkRocketEntity)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public FireworkRocketEntity getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (FireworkRocketEntity) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java +index eb10f94d5ed8ca89d3786138647dd43357609a6c..f4d92fb44fd7cee7debe3e283e8b672021e3e23f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java +@@ -10,8 +10,16 @@ public class CraftFish extends CraftWaterMob implements Fish, io.papermc.paper.e + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public AbstractFish getHandleRaw() { ++ return (AbstractFish)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractFish getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractFish) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +index e0d65df2e5b4c14abeb89a5f72cc2d9fa034dcf5..bd8f1925cb3eee30a5b5ea83225b6d94c80bc69a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +@@ -14,8 +14,16 @@ public class CraftFishHook extends CraftProjectile implements FishHook { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public FishingHook getHandleRaw() { ++ return (FishingHook)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public FishingHook getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (FishingHook) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java +index 8117faa0c89a966d057f4bf251c03a09d1e8797e..7c3827e6ef608ff15be9bced4788b09f1572aecb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java +@@ -10,8 +10,16 @@ public class CraftFlying extends CraftMob implements Flying { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public FlyingMob getHandleRaw() { ++ return (FlyingMob)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public FlyingMob getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (FlyingMob) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java +index bb2b59ce9775a0d1dd9828885e57c14cf40d9f04..90dcbf746c5effa98c09059552674a3e428ac1b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java +@@ -14,8 +14,16 @@ public class CraftFox extends CraftAnimals implements Fox { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Fox getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Fox)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Fox getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Fox) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java +index 58cacdc8f37420be6fac280a5fd295d1da40dba8..9bff52b31b3e8ef10707822685e194c3cd2f11ad 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java +@@ -18,8 +18,16 @@ public class CraftFrog extends CraftAnimals implements org.bukkit.entity.Frog { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public Frog getHandleRaw() { ++ return (Frog)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Frog getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Frog) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java +index 97fa4e1e70203194bd939618b2fad92665af6d59..27b309c9ce10798e3c3a7a9d39b8c300e471e177 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java +@@ -9,8 +9,16 @@ public class CraftGhast extends CraftFlying implements Ghast, CraftEnemy { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Ghast getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Ghast)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Ghast getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Ghast) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java +index 5826205339e99e2536b93c8589d95917749f8417..9bb22fc146012310bca849fccb0a1e7e987875e9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java +@@ -9,8 +9,16 @@ public class CraftGiant extends CraftMonster implements Giant { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Giant getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Giant)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Giant getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Giant) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java +index b9a7576d2481b64b7e5b46d66c1f55d1dc28c540..00c95313a233a032518e2435922d4044a9d67aee 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java +@@ -9,8 +9,16 @@ public class CraftGlowItemFrame extends CraftItemFrame implements GlowItemFrame + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.decoration.GlowItemFrame getHandleRaw() { ++ return (net.minecraft.world.entity.decoration.GlowItemFrame)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.decoration.GlowItemFrame getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.decoration.GlowItemFrame) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java +index 253a0d2f987163cbbb28d261674b47137cbbcbe2..1ed09d2aa4077165e9f88dd9db34f4083a2953c2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java +@@ -10,8 +10,16 @@ public class CraftGlowSquid extends CraftSquid implements GlowSquid { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.GlowSquid getHandleRaw() { ++ return (net.minecraft.world.entity.GlowSquid)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.GlowSquid getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.GlowSquid) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java +index 2c21de478bff9cdf13ba46cd041831d54c11e924..e64d7c4cfe65d34bdab13496741645f808f43dc6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java +@@ -9,8 +9,16 @@ public class CraftGoat extends CraftAnimals implements Goat { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.goat.Goat getHandleRaw() { ++ return (net.minecraft.world.entity.animal.goat.Goat)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.goat.Goat getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.goat.Goat) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java +index e27e469894bdd17cf7a004a85fdf0eaa746111a6..bbb8ff66580e62b5fb66aac22de72b9b9eafd3ef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java +@@ -9,8 +9,16 @@ public class CraftGolem extends CraftCreature implements Golem { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public AbstractGolem getHandleRaw() { ++ return (AbstractGolem)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractGolem getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractGolem) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java +index e232350f2c6ef1900b05fda4d3f94099057d10e5..2c411b569cc4b222ed3cdfb95237c86cd6a0fabb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java +@@ -13,8 +13,16 @@ public class CraftGuardian extends CraftMonster implements Guardian { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Guardian getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Guardian)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Guardian getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Guardian) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java +index f1e3f2b89bcd969f3c80548e165881a9b290eb53..2e4b86b44ace5eecefc9ab09c6e1f0a31247ad2f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java +@@ -57,8 +57,16 @@ public class CraftHanging extends CraftBlockAttachedEntity implements Hanging { + return CraftBlock.notchToBlockFace(direction); + } + ++ // Folia start - region threading ++ @Override ++ public HangingEntity getHandleRaw() { ++ return (HangingEntity)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public HangingEntity getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (HangingEntity) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java +index 37007775d27598e319c0c78929c6a808b697724a..b9819fc2c2ffc1a21a6e0973bb0d3595ee9c565d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java +@@ -51,8 +51,16 @@ public class CraftHoglin extends CraftAnimals implements Hoglin, CraftEnemy { + return this.getHandle().isConverting(); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.hoglin.Hoglin getHandleRaw() { ++ return (net.minecraft.world.entity.monster.hoglin.Hoglin)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.hoglin.Hoglin getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.hoglin.Hoglin) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java +index 9b6ff0f64966c78a3233860bb0840182b52f01bc..fb34651a9e4ed0cb05721d15524a26f89333d5e7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java +@@ -13,8 +13,16 @@ public class CraftHorse extends CraftAbstractHorse implements Horse { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.horse.Horse getHandleRaw() { ++ return (net.minecraft.world.entity.animal.horse.Horse)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.horse.Horse getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.horse.Horse) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index a396157548a5b3c3e86206c35789bb40346c701c..6e0290c3bce61465f1938263617ae1c90082852d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -306,8 +306,16 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + this.mode = mode; + } + ++ // Folia start - region threading ++ @Override ++ public Player getHandleRaw() { ++ return (Player)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Player getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Player) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java +index fb3c518f02cb4c428f022523d2f838625841332b..846a429493236f5002f0fae85c6cd7d20169dbe0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java +@@ -10,8 +10,16 @@ public class CraftIllager extends CraftRaider implements Illager { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public AbstractIllager getHandleRaw() { ++ return (AbstractIllager)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractIllager getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractIllager) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java +index 5b2af80e584977683cd39e6f440e65a76e929be9..789191168f74b3272e8da2131e0311853033c938 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java +@@ -9,8 +9,16 @@ public class CraftIllusioner extends CraftSpellcaster implements Illusioner, com + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Illusioner getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Illusioner)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Illusioner getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Illusioner) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java +index caa3016bf9742222205e3ea9a327fad3c4f912bb..2e00c7fe8dadd4c57c83a51cdfce165b6bfd6807 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java +@@ -12,8 +12,16 @@ public class CraftInteraction extends CraftEntity implements Interaction { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.Interaction getHandleRaw() { ++ return (net.minecraft.world.entity.Interaction)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.Interaction getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.Interaction) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java +index 63cae1a2e95d8da17c45c4404a8dd0ca6a413c39..e417ff87b047dcffa6121835af6f4e713526e16b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java +@@ -8,8 +8,16 @@ public class CraftIronGolem extends CraftGolem implements IronGolem { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.IronGolem getHandleRaw() { ++ return (net.minecraft.world.entity.animal.IronGolem)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.IronGolem getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.IronGolem) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index 7a3d982b133f8cdaeb936cf40f92565f0f7f6dd0..9216543d8f9c25221abb510b35c6bd504e2ccfeb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -18,8 +18,16 @@ public class CraftItem extends CraftEntity implements Item { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public ItemEntity getHandleRaw() { ++ return (ItemEntity)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public ItemEntity getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (ItemEntity) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java +index 787f91566fc53c2b4aeba1ec10d8f46ccf15cbe6..04a73a31ba09557e901ff1985dc5d5e53f18d99a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java +@@ -13,8 +13,16 @@ public class CraftItemDisplay extends CraftDisplay implements ItemDisplay { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.Display.ItemDisplay getHandleRaw() { ++ return (net.minecraft.world.entity.Display.ItemDisplay)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.Display.ItemDisplay getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.Display.ItemDisplay) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java +index 350ad61ab3fe66abd528e353b431a4a6dac17506..332f209980d3e645ad469fcebb93cc09253ebc20 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java +@@ -157,8 +157,16 @@ public class CraftItemFrame extends CraftHanging implements ItemFrame { + this.getHandle().fixed = fixed; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.decoration.ItemFrame getHandleRaw() { ++ return (net.minecraft.world.entity.decoration.ItemFrame)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.decoration.ItemFrame getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.decoration.ItemFrame) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java +index 0848963e61e03aa2a1740208ee372fd9edb7fc11..de2236f0106330ebe9d76bd308f9eee8751db826 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java +@@ -14,8 +14,16 @@ public class CraftLargeFireball extends CraftSizedFireball implements LargeFireb + this.getHandle().explosionPower = (int) yield; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.LargeFireball getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.LargeFireball)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.LargeFireball getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.LargeFireball) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java +index 76a7fc3d6c561d12bde17b9f93cae03a6cbb84b3..cd1ba99a75da644d06c4eb2f2c1ff91bfa5afa01 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java +@@ -24,6 +24,13 @@ public class CraftLeash extends CraftBlockAttachedEntity implements LeashHitch { + return BlockFace.SELF; + } + ++ // Folia start - region threading ++ @Override ++ public LeashFenceKnotEntity getHandleRaw() { ++ return (LeashFenceKnotEntity)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public BlockFace getAttachedFace() { + // Leash hitch has no facing direction, so we return self +@@ -37,6 +44,7 @@ public class CraftLeash extends CraftBlockAttachedEntity implements LeashHitch { + + @Override + public LeashFenceKnotEntity getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (LeashFenceKnotEntity) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +index e9f471e60af0725ec34e2985d63ae9ea9f88590a..cd824fc65ac2b1fe55710da4700f7c31f820f205 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +@@ -41,8 +41,16 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike + this.getHandle().setCause((player != null) ? ((CraftPlayer) player).getHandle() : null); + } + ++ // Folia start - region threading ++ @Override ++ public LightningBolt getHandleRaw() { ++ return (LightningBolt)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public LightningBolt getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (LightningBolt) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 4f98d138a275a6c34528b7a5148ef265bc38d6b5..228f7fbce72b7828905e21f21525371a92ec07d4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -487,6 +487,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + this.getHandle().invulnerableTime = ticks; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.LivingEntity getHandleRaw() { ++ return (net.minecraft.world.entity.LivingEntity)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public int getNoActionTicks() { + return this.getHandle().getNoActionTime(); +@@ -500,6 +507,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public net.minecraft.world.entity.LivingEntity getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.LivingEntity) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +index 351f42842b780d053cd2e5bad9ae299449141b10..63513eff9b849f240b16ea28060b78c774e23934 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +@@ -14,8 +14,16 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.horse.Llama getHandleRaw() { ++ return (net.minecraft.world.entity.animal.horse.Llama)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.horse.Llama getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.horse.Llama) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java +index 47633f05b4fab1dcabc2117e7645fe6d6949622a..5e51d6eeda2abdc5df9c9a280a191ca1cbf615b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java +@@ -10,8 +10,16 @@ public class CraftLlamaSpit extends AbstractProjectile implements LlamaSpit { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.LlamaSpit getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.LlamaSpit)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.LlamaSpit getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.LlamaSpit) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java +index 58b638ffd338e1b0f4962490c665c1eebcf33dcc..9f1b4d0561c10fbbfe0daec3d9dabfdaca9cf70b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java +@@ -9,8 +9,16 @@ public class CraftMagmaCube extends CraftSlime implements MagmaCube { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.MagmaCube getHandleRaw() { ++ return (net.minecraft.world.entity.monster.MagmaCube)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.MagmaCube getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.MagmaCube) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java +index e6782a48d22ba1e683e3fe463e970e8a5ed60fbd..afaa4570c1991cd4260ffcdba823ba2452ad156a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java +@@ -9,8 +9,16 @@ public class CraftMarker extends CraftEntity implements Marker { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.Marker getHandleRaw() { ++ return (net.minecraft.world.entity.Marker)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.Marker getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.Marker) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +index b42bce0c4f4b3aac2729cfdad392d863245ed693..d3ffa2b4402fdd005104d07d92e4066c6170615e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +@@ -77,8 +77,16 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { + } + // Paper end + ++ // Folia start - region threading ++ @Override ++ public AbstractMinecart getHandleRaw() { ++ return (AbstractMinecart)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractMinecart getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractMinecart) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java +index f34fa6715e477936097367a7aefd1a2bf87d3d90..e5310b138b13d54448072c15f6768acc1c33a45c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java +@@ -20,8 +20,16 @@ public class CraftMinecartCommand extends CraftMinecart implements CommandMineca + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public MinecartCommandBlock getHandleRaw() { ++ return (MinecartCommandBlock)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public MinecartCommandBlock getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (MinecartCommandBlock) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java +index 451f3a6f0b47493da3af3f5d6baced6a8c97f350..d4f98fe5eb5e463679ebc5b82b077c98e4448203 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java +@@ -13,8 +13,16 @@ public abstract class CraftMinecartContainer extends CraftMinecart implements co + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public AbstractMinecartContainer getHandleRaw() { ++ return (AbstractMinecartContainer)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractMinecartContainer getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractMinecartContainer) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java +index 1be1f6d23f2224d4d8720d40f2e530736b1bae81..eee08d53714b485bffd1398506ed0cb3b7002d2c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java +@@ -11,8 +11,16 @@ public class CraftMinecartFurnace extends CraftMinecart implements PoweredMineca + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public MinecartFurnace getHandleRaw() { ++ return (MinecartFurnace)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public MinecartFurnace getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (MinecartFurnace) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +index 3a3563a1bdbc0d84d973b3a04b50b78b4bc3d379..1e86ce7c1a3fc1f4eae2d8136fc0d879fbde5301 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java +@@ -34,8 +34,17 @@ public final class CraftMinecartHopper extends CraftMinecartContainer implements + ((MinecartHopper) this.getHandle()).setEnabled(enabled); + } + // Paper start ++ ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.vehicle.MinecartHopper getHandleRaw() { ++ return (net.minecraft.world.entity.vehicle.MinecartHopper)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.vehicle.MinecartHopper getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.vehicle.MinecartHopper) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java +index e8ece01669373ecf6552d33b2ed72668524e2650..fbb5c2e2a136cd03eb1f4b4b5ef289d6a6c39173 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java +@@ -162,8 +162,16 @@ final class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMine + this.getHandle().getSpawner().spawnRange = spawnRange; + } + ++ // Folia start - region threading ++ @Override ++ public MinecartSpawner getHandleRaw() { ++ return (MinecartSpawner)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public MinecartSpawner getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (MinecartSpawner) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java +index 15184e7fc3aeb388fb9de6be2ad72f98fee52044..f18093c5ccacfb55e7c6133cf5212c464e41ead4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java +@@ -72,8 +72,16 @@ public final class CraftMinecartTNT extends CraftMinecart implements ExplosiveMi + this.getHandle().explode(power); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.vehicle.MinecartTNT getHandleRaw() { ++ return (net.minecraft.world.entity.vehicle.MinecartTNT)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public MinecartTNT getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (MinecartTNT) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index 778a9d3f8bfe5dba59e1e655e4eeb8822678b8cf..b4ec6c1f8ea5d5c34f2ecb2b066e49993ae79dc7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -54,8 +54,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob, io.pape + return (sound != null) ? CraftSound.minecraftToBukkit(sound) : null; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.Mob getHandleRaw() { ++ return (net.minecraft.world.entity.Mob)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.Mob getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.Mob) this.entity; + } + +@@ -63,7 +71,7 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob, io.pape + @Override + public void setHandle(net.minecraft.world.entity.Entity entity) { + super.setHandle(entity); +- paperPathfinder.setHandle(getHandle()); ++ paperPathfinder.setHandle((net.minecraft.world.entity.Mob)entity); // Folia - region threading + } + // Paper end - Mob Pathfinding API + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java +index 706c74c832f6893df3797023f68add31139c7d57..1cf155fc23f13691f86673eac3084d7530d69ab5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java +@@ -9,8 +9,16 @@ public class CraftMonster extends CraftCreature implements Monster, CraftEnemy { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Monster getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Monster)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Monster getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Monster) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java +index 596146ad7899c21645df8834ce5f0afd6c1b0604..78f6e16a745924419d5aad53f95d767d87bdf5d0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java +@@ -19,6 +19,13 @@ public class CraftMushroomCow extends CraftCow implements MushroomCow, io.paperm + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.MushroomCow getHandleRaw() { ++ return (net.minecraft.world.entity.animal.MushroomCow)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public boolean hasEffectsForNextStew() { + SuspiciousStewEffects stewEffects = this.getHandle().stewEffects; +@@ -94,6 +101,7 @@ public class CraftMushroomCow extends CraftCow implements MushroomCow, io.paperm + + @Override + public net.minecraft.world.entity.animal.MushroomCow getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.MushroomCow) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java +index 5c60a30e80448fbf04b5fa4b1ef12fb2ee99bfd5..4ba52939450c0a89e5ba1fa57a84b3ceccb9fef0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java +@@ -9,8 +9,16 @@ public class CraftOcelot extends CraftAnimals implements Ocelot { + super(server, ocelot); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Ocelot getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Ocelot)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Ocelot getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Ocelot) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java +index ecdac2cf74e99f0d69e053dece11ab891973dc2b..fa365c38c9e0f671df1481c8b36bc993eee42afd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java +@@ -13,6 +13,7 @@ public class CraftOminousItemSpawner extends CraftEntity implements OminousItemS + + @Override + public net.minecraft.world.entity.OminousItemSpawner getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.OminousItemSpawner) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java +index bcac1359c667ef1ee46384f9c7a5adf4010d2b08..e740abd53d99f549acb5048d748241560dfeddd1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java +@@ -50,8 +50,16 @@ public class CraftPainting extends CraftHanging implements Painting { + return false; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.decoration.Painting getHandleRaw() { ++ return (net.minecraft.world.entity.decoration.Painting)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.decoration.Painting getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.decoration.Painting) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java +index 01d104d91de9e1319d27e39d3f474318c7809486..c298b263175dc82097c0ad2c35194f3e326c6658 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java +@@ -11,8 +11,16 @@ public class CraftPanda extends CraftAnimals implements Panda { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Panda getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Panda)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Panda getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Panda) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java +index 04d6cf6a1f3ae8316e3b2862c2d1b04e84a3b20a..4ed79610b50be635a7a7c8a8f7d7af8f91ce2d0d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java +@@ -11,8 +11,16 @@ public class CraftParrot extends CraftTameableAnimal implements Parrot { + super(server, parrot); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Parrot getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Parrot)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Parrot getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Parrot) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index 429200b0b06cc0f71db03924228240b8b5f22a55..634a95a5d89821d3464e2ae8bd86b3b574f0ef17 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -9,8 +9,16 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Phantom getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Phantom)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Phantom getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Phantom) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java +index fd4f13e8ea000eb38efd77bfb197855db8816744..7f049e504cf7af7c5c5ee247bccb4f6e01b80121 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java +@@ -55,8 +55,16 @@ public class CraftPig extends CraftAnimals implements Pig { + return Material.CARROT_ON_A_STICK; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Pig getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Pig)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Pig getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Pig) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java +index 49beb836d2801aadf869feefa602616daebe633f..d220874f678649acfae549691262c370f0228908 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java +@@ -30,8 +30,16 @@ public class CraftPigZombie extends CraftZombie implements PigZombie { + return this.getAnger() > 0; + } + ++ // Folia start - region threading ++ @Override ++ public ZombifiedPiglin getHandleRaw() { ++ return (ZombifiedPiglin)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public ZombifiedPiglin getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (ZombifiedPiglin) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +index 5124a383b60b2c8de89fa992547d0c61db760c21..d75230de45102434660b3b7926a804d26e10ab2c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +@@ -75,8 +75,16 @@ public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.dest + return new CraftInventory(this.getHandle().inventory); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.piglin.Piglin getHandleRaw() { ++ return (net.minecraft.world.entity.monster.piglin.Piglin)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.piglin.Piglin getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.piglin.Piglin) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java +index e7957d6051244ba410f8633f9c16eeb8c5ac3ce0..f8465f75c15d96ccd82ee394c9e658966837ad07 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java +@@ -95,8 +95,16 @@ public class CraftPiglinAbstract extends CraftMonster implements PiglinAbstract + public void setBreed(boolean b) { + } + ++ // Folia start - region threading ++ @Override ++ public AbstractPiglin getHandleRaw() { ++ return (AbstractPiglin)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public AbstractPiglin getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (AbstractPiglin) super.getHandle(); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java +index be874dc973fe632e8ace86041392ca69beaefd16..efb64160089eeb6be8faf7790989909145c22a4b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java +@@ -9,8 +9,16 @@ public class CraftPiglinBrute extends CraftPiglinAbstract implements PiglinBrute + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.piglin.PiglinBrute getHandleRaw() { ++ return (net.minecraft.world.entity.monster.piglin.PiglinBrute)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.piglin.PiglinBrute getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.piglin.PiglinBrute) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java +index 2638c341bc02f201f7ab17fdebcdbdf3a7ec05bf..074b2919be2b5544b0a46e6cd32f6c57dad6bfdc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java +@@ -11,8 +11,16 @@ public class CraftPillager extends CraftIllager implements Pillager, com.destroy + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Pillager getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Pillager)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Pillager getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Pillager) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 3260f20b667918dd7cd641d5d96688721fce2f9c..7723b2fbc8a1d1907ded37d7530e34d3cac8bdce 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -673,7 +673,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void kickPlayer(String message) { +- org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot ++ //org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot // Folia - thread-safe now, as it will simply delay the kick + this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true), org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause + } + +@@ -691,7 +691,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { +- org.spigotmc.AsyncCatcher.catchOp("player kick"); ++ //org.spigotmc.AsyncCatcher.catchOp("player kick"); // Folia - region threading - no longer needed + final ServerGamePacketListenerImpl connection = this.getHandle().connection; + if (connection != null) { + connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); +@@ -1411,6 +1411,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { ++ // Folia start - region threading ++ if (true) { ++ throw new UnsupportedOperationException("Must use teleportAsync while in region threading"); ++ } ++ // Folia end - region threading + Set relativeArguments; + Set allFlags; + if (flags.length == 0) { +@@ -2075,7 +2080,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + private void unregisterEntity(Entity other) { + // Paper end + ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; +- ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); ++ ChunkMap.TrackedEntity entry = other.moonrise$getTrackedEntity(); // Folia - region threading + if (entry != null) { + entry.removePlayer(this.getHandle()); + } +@@ -2172,7 +2177,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + if (original != null) otherPlayer.setUUID(original); // Paper - uuid override + } + +- ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); ++ ChunkMap.TrackedEntity entry = other.moonrise$getTrackedEntity(); // Folia - region threading + if (entry != null && !entry.seenBy.contains(this.getHandle().connection)) { + entry.updatePlayer(this.getHandle()); + } +@@ -2321,9 +2326,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return this; + } + ++ // Folia start - region threading ++ @Override ++ public ServerPlayer getHandleRaw() { ++ return (ServerPlayer)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public ServerPlayer getHandle() { +- return (ServerPlayer) this.entity; ++ return (ServerPlayer) this.entity; // Folia - region threading - no checks for players, as it's a total mess + } + + public void setHandle(final ServerPlayer entity) { +@@ -3355,7 +3367,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + { + if ( CraftPlayer.this.getHealth() <= 0 && CraftPlayer.this.isOnline() ) + { +- CraftPlayer.this.server.getServer().getPlayerList().respawn( CraftPlayer.this.getHandle(), false, Entity.RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.PLUGIN ); ++ CraftPlayer.this.getHandle().respawn(null, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.PLUGIN); // Folia - region threading + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java +index fe075cfdf3097d6cb768e71b8cc360abb8eaf367..657886dfb8e152ed4a64a64878da23526dad0160 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java +@@ -8,8 +8,17 @@ public class CraftPolarBear extends CraftAnimals implements PolarBear { + public CraftPolarBear(CraftServer server, net.minecraft.world.entity.animal.PolarBear entity) { + super(server, entity); + } ++ ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.PolarBear getHandleRaw() { ++ return (net.minecraft.world.entity.animal.PolarBear)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.PolarBear getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.PolarBear) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java +index 4f1fa7dec78970bdfc184d3c1f1632dc9d75a574..99fd39c60d1b0a50bddf7b9b9f45f22c189a2f25 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java +@@ -12,8 +12,16 @@ public abstract class CraftProjectile extends AbstractProjectile implements Proj + + // Paper - moved to AbstractProjectile + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.Projectile getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.Projectile)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.Projectile getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.Projectile) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java +index 35a8219734633529325430810e88755b2dd23125..7ba16121cb1828cf5c0ff8f027fa05e9c1814ffa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java +@@ -10,8 +10,16 @@ public class CraftPufferFish extends CraftFish implements PufferFish { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public Pufferfish getHandleRaw() { ++ return (Pufferfish)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Pufferfish getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Pufferfish) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java +index 519ef701a7d6534f7cb516f6296b95ee521f661d..6407b4e6ca793a676e7d669920ae90b762207970 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java +@@ -10,8 +10,16 @@ public class CraftRabbit extends CraftAnimals implements Rabbit { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Rabbit getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Rabbit)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Rabbit getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Rabbit) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java +index 763c368e299588f9a0e085a8a5e04e97e1f33428..3e85638f3941c2085a7ddb102d0ccc23446cc1d6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java +@@ -16,8 +16,16 @@ public abstract class CraftRaider extends CraftMonster implements Raider { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.raid.Raider getHandleRaw() { ++ return (net.minecraft.world.entity.raid.Raider)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.raid.Raider getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.raid.Raider) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java +index 09796ce15658e3f7c223a265a547a51ee729ed40..bfca2951d18f7451787877b5a6503b0572945447 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java +@@ -9,8 +9,16 @@ public class CraftRavager extends CraftRaider implements Ravager { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Ravager getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Ravager)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Ravager getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Ravager) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java +index 7660cc21e936002ebb23510f0ec2b58d71e5157d..a13976b2712413ef9fdeecd1e3ca762238d4efd9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java +@@ -10,8 +10,16 @@ public class CraftSalmon extends io.papermc.paper.entity.PaperSchoolableFish imp + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Salmon getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Salmon)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Salmon getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Salmon) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java +index 37291d7ad9fdf0fe78894f82a418f40bb581f58b..6c7e54a929b46fd160726e41bf63023a8622d044 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java +@@ -29,8 +29,16 @@ public class CraftSheep extends CraftAnimals implements Sheep, io.papermc.paper. + this.getHandle().setSheared(flag); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Sheep getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Sheep)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Sheep getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Sheep) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java +index 05ec06b71642ab1ef03829039f7ac1e4c527ee50..1e1e908cbc08df06996128e3dd6d277a19f9a2df 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java +@@ -18,8 +18,16 @@ public class CraftShulker extends CraftGolem implements Shulker, CraftEnemy { + return "CraftShulker"; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Shulker getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Shulker)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Shulker getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Shulker) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java +index b3797a43eeee11cb7ae0774d61bd5f195d0aa3ad..d045d50d1cfccb696153b8c33e86e193194271fc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java +@@ -69,8 +69,16 @@ public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBul + return "CraftShulkerBullet"; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.ShulkerBullet getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.ShulkerBullet)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.ShulkerBullet getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.ShulkerBullet) this.entity; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java +index 7c75d78e5e28d7320c6dbe979bcd576658fb310b..a25ca7fa49a3bb213f6af5804079b2efe43ef0e4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java +@@ -8,8 +8,16 @@ public class CraftSilverfish extends CraftMonster implements Silverfish { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Silverfish getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Silverfish)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Silverfish getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Silverfish) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java +index de3327812c08b3bb8f5907ae657f67962d1e4e8b..c479f4adb945e8bb6ea2279ad23d679ca0dee606 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java +@@ -27,8 +27,16 @@ public class CraftSizedFireball extends CraftFireball implements SizedFireball { + this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); + } + ++ // Folia start - region threading ++ @Override ++ public Fireball getHandleRaw() { ++ return (Fireball)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Fireball getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Fireball) this.entity; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +index 6f98da9be6aef35e3b5c940188b872459a383c8e..dc93b8aaf48671d66d3bb3fb413b83fc4b4b26cf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +@@ -31,8 +31,16 @@ public class CraftSkeleton extends CraftAbstractSkeleton implements Skeleton { + } + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Skeleton getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Skeleton)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Skeleton getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Skeleton) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java +index fbb47491dcc75f8247dee9f123f946f99ef1467f..6cc1ea31340298037c2a00d64d70928f31278a4a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java +@@ -20,8 +20,16 @@ public class CraftSkeletonHorse extends CraftAbstractHorse implements SkeletonHo + return Variant.SKELETON_HORSE; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.horse.SkeletonHorse getHandleRaw() { ++ return (net.minecraft.world.entity.animal.horse.SkeletonHorse)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.horse.SkeletonHorse getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.horse.SkeletonHorse) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +index e48f7d1cbec4a2319745ba48a5d44ab9925214e2..27b07865edfa659d9cdfcf2d84935ad313472e87 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +@@ -19,8 +19,16 @@ public class CraftSlime extends CraftMob implements Slime, CraftEnemy { + this.getHandle().setSize(size, /* true */ getHandle().isAlive()); // Paper - fix dead slime setSize invincibility + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Slime getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Slime)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Slime getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Slime) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java +index 072df206858944ef78179b0a6d61ed990a844d2b..71625cc4e4b2fd3773baf1b2c1ea7e463b854ffa 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java +@@ -8,8 +8,16 @@ public class CraftSmallFireball extends CraftSizedFireball implements SmallFireb + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.SmallFireball getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.SmallFireball)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.SmallFireball getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.SmallFireball) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java +index 555337018fe218ac5a296a5e6a1d82720fee05e1..873b7e7a05b3465b79a82ed583ce16bb245ebcbf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java +@@ -16,8 +16,16 @@ public class CraftSniffer extends CraftAnimals implements Sniffer { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.sniffer.Sniffer getHandleRaw() { ++ return (net.minecraft.world.entity.animal.sniffer.Sniffer)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.sniffer.Sniffer getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.sniffer.Sniffer) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java +index d959825fd11a94aba175934cd7739544a23958fc..9f53ba11a2adabdebd70eee5a811fec7dccd7b10 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java +@@ -8,8 +8,16 @@ public class CraftSnowball extends CraftThrowableProjectile implements Snowball + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.Snowball getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.Snowball)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.Snowball getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.Snowball) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +index 4ce2373ff71c3c1b8951646e057587a3ab09e145..6f88f18fc23cb793d4394b80201e40b09a0a7f9d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +@@ -19,8 +19,16 @@ public class CraftSnowman extends CraftGolem implements Snowman, com.destroystok + this.getHandle().setPumpkin(!derpMode); + } + ++ // Folia start - region threading ++ @Override ++ public SnowGolem getHandleRaw() { ++ return (SnowGolem)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public SnowGolem getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (SnowGolem) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java +index 70f1f8740091d5a3d5983227ef2e6e166bb6ce7e..4886c9ba4bf952415ee4b1395adfeca8d928cdf5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java +@@ -9,8 +9,16 @@ public class CraftSpectralArrow extends CraftAbstractArrow implements SpectralAr + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.SpectralArrow getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.SpectralArrow)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.SpectralArrow getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.SpectralArrow) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java +index 525827f1747631fa108be7e1b7395b47d33aa397..3ec5d458a895300da462f63bae683980a741e477 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java +@@ -12,8 +12,16 @@ public class CraftSpellcaster extends CraftIllager implements Spellcaster { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public SpellcasterIllager getHandleRaw() { ++ return (SpellcasterIllager)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public SpellcasterIllager getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (SpellcasterIllager) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java +index b4afc37c21fc478df44fca7ec3fbc33d337dc6b7..bf3236f673118539d7cfb883bcdf84de7ae5bd73 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java +@@ -9,8 +9,16 @@ public class CraftSpider extends CraftMonster implements Spider { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Spider getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Spider)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Spider getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Spider) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java +index 067a95ea50418601acfb8b9453d1291161bb706a..3a41ef5fdecee262f3e8899deec360c35ddf1b6f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java +@@ -9,8 +9,16 @@ public class CraftSquid extends CraftAgeable implements Squid { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Squid getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Squid)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Squid getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Squid) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java +index 74fac97231d4d89d1b941a1b5295afc2dafc6007..27992471bb7727a17f5fee61046cc0718994403a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java +@@ -65,8 +65,16 @@ public class CraftStrider extends CraftAnimals implements Strider { + return Material.WARPED_FUNGUS_ON_A_STICK; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Strider getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Strider)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Strider getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Strider) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java +index a61aec087fa7cec27a803668bdc1b9e6eb336755..1c3826dc868a78402531b6abdddd017c83dae853 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java +@@ -42,8 +42,16 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed { + this.getHandle().setFuse(fuseTicks); + } + ++ // Folia start - region threading ++ @Override ++ public PrimedTnt getHandleRaw() { ++ return (PrimedTnt)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public PrimedTnt getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (PrimedTnt) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java +index d7c6a0bbc5671ea8f2488230c94df5146a1e98b9..ea001c3e91478cde59eb6b7663013d43554e5fb5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java +@@ -9,8 +9,16 @@ public class CraftTadpole extends CraftFish implements org.bukkit.entity.Tadpole + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public Tadpole getHandleRaw() { ++ return (Tadpole)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Tadpole getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Tadpole) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java +index cedb8e67e208cdf954d052a4f0a100c1c07a962b..8bf3936ad7a42a98a14e82fcabd238712e8532c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java +@@ -12,8 +12,16 @@ public class CraftTameableAnimal extends CraftAnimals implements Tameable, Creat + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public TamableAnimal getHandleRaw() { ++ return (TamableAnimal)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public TamableAnimal getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (TamableAnimal) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java +index 3dd5009a9e484dc3f3a6ddea95aab96e0f2f67df..ebd5576b467b94bbaae467ad06c86afbe28fad36 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java +@@ -13,8 +13,16 @@ public class CraftTextDisplay extends CraftDisplay implements TextDisplay { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.Display.TextDisplay getHandleRaw() { ++ return (net.minecraft.world.entity.Display.TextDisplay)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.Display.TextDisplay getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.Display.TextDisplay) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java +index bf7b111abdf42969218a3608d86a3313432bc0a0..b2b1b7ad56d0adc452b32a866fa0c6682fcd4882 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java +@@ -26,8 +26,16 @@ public abstract class CraftThrowableProjectile extends CraftProjectile implement + this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); + } + ++ // Folia start - region threading ++ @Override ++ public ThrowableItemProjectile getHandleRaw() { ++ return (ThrowableItemProjectile)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public ThrowableItemProjectile getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (ThrowableItemProjectile) this.entity; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java +index 5e7fef664c56d6087502e56a0eb4fc07d34ade9f..00d578700c09cab5b5ae99bcb27fa17048ac24b1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java +@@ -9,8 +9,16 @@ public class CraftThrownExpBottle extends CraftThrowableProjectile implements Th + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public ThrownExperienceBottle getHandleRaw() { ++ return (ThrownExperienceBottle)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public ThrownExperienceBottle getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (ThrownExperienceBottle) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +index 65b6de9d21da6843d7c7087f0dea98d3b75f24cf..8988f2a1e3fe6a296c245e893ddb927da1d59167 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +@@ -61,8 +61,17 @@ public class CraftThrownPotion extends CraftThrowableProjectile implements Throw + this.getHandle().splash(null); + } + // Paper end ++ ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.ThrownPotion getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.ThrownPotion)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.ThrownPotion getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.ThrownPotion) this.entity; + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java +index 4b3a764114c8372e1549dadeeced26dc7727f2d1..b800efe68124c27f97114a69a096fca2d66e671e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java +@@ -9,8 +9,16 @@ public class CraftTraderLlama extends CraftLlama implements TraderLlama { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.horse.TraderLlama getHandleRaw() { ++ return (net.minecraft.world.entity.animal.horse.TraderLlama)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.horse.TraderLlama getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.horse.TraderLlama) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +index 4fc893378fb0568ddcffc7593d66df6bfe23f659..5ddc96b17ddbd152929b0548bfedc802bd6dd7ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +@@ -12,8 +12,16 @@ public class CraftTrident extends CraftAbstractArrow implements Trident { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public ThrownTrident getHandleRaw() { ++ return (ThrownTrident)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public ThrownTrident getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (ThrownTrident) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java +index 9e53c30801c700719c78c0fd521fd615c94e02c8..11884c20e73846ec95288edcb514d3ae638eb803 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java +@@ -13,8 +13,16 @@ public class CraftTropicalFish extends io.papermc.paper.entity.PaperSchoolableFi + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.TropicalFish getHandleRaw() { ++ return (net.minecraft.world.entity.animal.TropicalFish)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.TropicalFish getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.TropicalFish) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +index 00e59cdc8c0b954eed84c611e91d00dfd5676ec1..c9589005410925cb88955314eb4c5e1c9ea78a8e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +@@ -9,8 +9,16 @@ public class CraftTurtle extends CraftAnimals implements Turtle { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Turtle getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Turtle)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Turtle getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Turtle) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +index e9ec3455eabc473e104b5342a615a38c1ac25a4f..3a65ae7e6ac1894855e4eafecc9c2bb87476298f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +@@ -13,8 +13,16 @@ public class CraftVex extends CraftMonster implements Vex { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Vex getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Vex)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Vex getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Vex) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index aaddce10e1d41531939d1e7f3d717b458ec1b7ab..bd515cdfbcc6fb0d17f70150d8b0bab60949db49 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -32,8 +32,16 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.npc.Villager getHandleRaw() { ++ return (net.minecraft.world.entity.npc.Villager)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.npc.Villager getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.npc.Villager) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java +index 3aa23d9f22d5cd22231293fd7d1ca4cb79eb7cb3..e705d49eafcf1def6e849bfc0ded4b7269a40ffb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java +@@ -14,8 +14,16 @@ public class CraftVillagerZombie extends CraftZombie implements ZombieVillager { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.ZombieVillager getHandleRaw() { ++ return (net.minecraft.world.entity.monster.ZombieVillager)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.ZombieVillager getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.ZombieVillager) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java +index bcd3370bc48520ea4bb53af25b892131d6ca0b33..8be282b028bc30056afc8852e8f47b287b238e73 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java +@@ -9,8 +9,16 @@ public class CraftVindicator extends CraftIllager implements Vindicator { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Vindicator getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Vindicator)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Vindicator getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Vindicator) super.getHandle(); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +index 3cceefa0d6278924a19641a49bdf16bcdacb2233..07d6b1296aeee0de3455380a8aeaedc8a9344735 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +@@ -9,8 +9,16 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.npc.WanderingTrader getHandleRaw() { ++ return (net.minecraft.world.entity.npc.WanderingTrader)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.npc.WanderingTrader getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.npc.WanderingTrader) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java +index c284eb96a1e330078076cbe61f0f6e2ff4ed89bd..a53dee61a4669ac9c1d051ad9f881230a186e92c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java +@@ -15,8 +15,16 @@ public class CraftWarden extends CraftMonster implements org.bukkit.entity.Warde + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public Warden getHandleRaw() { ++ return (Warden)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public Warden getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (Warden) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java +index 1b347deb6eb0b39c4a23936f7cd387421f06350d..4f26f0caca8a97d7770a569a65c1addaf6e9512c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java +@@ -10,8 +10,16 @@ public class CraftWaterMob extends CraftCreature implements WaterMob { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public WaterAnimal getHandleRaw() { ++ return (WaterAnimal)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public WaterAnimal getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (WaterAnimal) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java +index 46447b9651dc48181916ce1306ee5deec397be12..c26120711251a17b558a97ae0e20789d5c33b104 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java +@@ -10,6 +10,7 @@ public class CraftWindCharge extends CraftAbstractWindCharge implements WindChar + + @Override + public net.minecraft.world.entity.projectile.windcharge.WindCharge getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.windcharge.WindCharge) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +index 4b3d783cabcb2de1a67d7fbfb6f525bfb493aed1..216c97fb1d611b84322927c6eb97871dd05cf600 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java +@@ -15,8 +15,16 @@ public class CraftWitch extends CraftRaider implements Witch, com.destroystokyo. + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Witch getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Witch)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Witch getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Witch) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index 7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e..077b5685ccd1b5972ef92aa759ebabe5ec6d23c6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -21,8 +21,16 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok + } + } + ++ // Folia start - region threading ++ @Override ++ public WitherBoss getHandleRaw() { ++ return (WitherBoss)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public WitherBoss getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (WitherBoss) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java +index bc978391255c9414e06ff393f2e6707d329d020a..8d436a1453c8a66422c2a735764273176a6a4545 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java +@@ -18,8 +18,16 @@ public class CraftWitherSkull extends CraftFireball implements WitherSkull { + return this.getHandle().isDangerous(); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.projectile.WitherSkull getHandleRaw() { ++ return (net.minecraft.world.entity.projectile.WitherSkull)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.projectile.WitherSkull getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.projectile.WitherSkull) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +index c1b7f1281fbd41e765d2c1881763ca25b20e924d..913f68be4bf6c8af2765c2f6eddda88bdefe3382 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +@@ -30,8 +30,16 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { + } + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.animal.Wolf getHandleRaw() { ++ return (net.minecraft.world.entity.animal.Wolf)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.animal.Wolf getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.animal.Wolf) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java +index c134c4bb8c0377ceb7f8a5c40c94fd6312a9e448..d334e4a3ea075670e0aa7ea1429ffe4231eb0559 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java +@@ -19,8 +19,16 @@ public class CraftZoglin extends CraftMonster implements Zoglin { + this.getHandle().setBaby(flag); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Zoglin getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Zoglin)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Zoglin getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Zoglin) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +index dfc2b40e20069705f92d86a6898e3e8348bf4dcd..9e158d32dc13f8890511de1496d9d5b4c1956e3b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +@@ -12,8 +12,16 @@ public class CraftZombie extends CraftMonster implements Zombie { + super(server, entity); + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.entity.monster.Zombie getHandleRaw() { ++ return (net.minecraft.world.entity.monster.Zombie)this.entity; ++ } ++ // Folia end - region threading ++ + @Override + public net.minecraft.world.entity.monster.Zombie getHandle() { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading + return (net.minecraft.world.entity.monster.Zombie) this.entity; + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 57c3f8531bf85b53af3a4aad6e9e369d5cff0ce3..b1504bc0f06f930669a8d0787eb420416f7c2671 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -951,7 +951,7 @@ public class CraftEventFactory { + return CraftEventFactory.handleBlockSpreadEvent(world, source, target, block, 2); + } + +- public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. ++ public static final ThreadLocal sourceBlockOverrideRT = new ThreadLocal<>(); // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // Folia - region threading + + public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) { + // Suppress during worldgen +@@ -963,7 +963,7 @@ public class CraftEventFactory { + CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag); + state.setData(block); + +- BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state); ++ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), state); // Folia - region threading + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +@@ -2232,7 +2232,7 @@ public class CraftEventFactory { + CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); +- if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { ++ if (!net.minecraft.world.level.block.DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading + if (!event.callEvent()) { + return itemStack; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 1354ccfbf525e5e64483ac5f443cc2325ba63850..fad85bea8643a3a88ec5c4194de7a5060e81c136 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -514,6 +514,7 @@ public class CraftScheduler implements BukkitScheduler { + } + + protected CraftTask handle(final CraftTask task, final long delay) { // Paper ++ if (true) throw new UnsupportedOperationException(); // Folia - region threading + // Paper start + if (!this.isAsyncScheduler && !task.isSync()) { + this.asyncScheduler.handle(task, delay); +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +index 253574890a9ed23d38a84680ba1eb221dc72b310..ce8b91f00f925960ad17f381162a11294e8b511d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +@@ -45,6 +45,7 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + } + @Override + public CraftObjective registerNewObjective(String name, Criteria criteria, net.kyori.adventure.text.Component displayName, RenderType renderType) throws IllegalArgumentException { ++ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet + if (displayName == null) { + displayName = net.kyori.adventure.text.Component.empty(); + } +@@ -204,6 +205,7 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + + @Override + public Team registerNewTeam(String name) { ++ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet + Preconditions.checkArgument(name != null, "Team name cannot be null"); + Preconditions.checkArgument(name.length() <= Short.MAX_VALUE, "Team name '%s' is longer than the limit of 32767 characters (%s)", name, name.length()); + Preconditions.checkArgument(this.board.getPlayerTeam(name) == null, "Team name '%s' is already in use", name); +@@ -231,6 +233,7 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + + @Override + public void clearSlot(DisplaySlot slot) { ++ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet + Preconditions.checkArgument(slot != null, "Slot cannot be null"); + this.board.setDisplayObjective(CraftScoreboardTranslations.fromBukkitSlot(slot), null); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +index f3184be3853dfc4df4ae4b8af764dfef07628ef4..99ba4d19b72a66ea1fc83fda16d37aaa0f154abb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +@@ -42,6 +42,7 @@ public final class CraftScoreboardManager implements ScoreboardManager { + + @Override + public CraftScoreboard getNewScoreboard() { ++ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet + org.spigotmc.AsyncCatcher.catchOp("scoreboard creation"); // Spigot + CraftScoreboard scoreboard = new CraftScoreboard(new ServerScoreboard(this.server)); + // Paper start +@@ -68,6 +69,7 @@ public final class CraftScoreboardManager implements ScoreboardManager { + + // CraftBukkit method + public void setPlayerBoard(CraftPlayer player, org.bukkit.scoreboard.Scoreboard bukkitScoreboard) { ++ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet + Preconditions.checkArgument(bukkitScoreboard instanceof CraftScoreboard, "Cannot set player scoreboard to an unregistered Scoreboard"); + + CraftScoreboard scoreboard = (CraftScoreboard) bukkitScoreboard; +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 59c0e8dbe2d0d1155487de33c680e41c0b61acac..68cc7eb8d7357d357d9d57d6391bf5a4a09221de 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -379,6 +379,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + throw new InvalidPluginException("Unsupported API version " + pdf.getAPIVersion()); + } + ++ // Folia start - block plugins not marked as supported ++ if (!pdf.isFoliaSupported()) { ++ throw new InvalidPluginException("Plugin " + pdf.getFullName() + " is not marked as supporting regionised multithreading"); ++ } ++ // Folia end - block plugins not marked as supported ++ + if (toCheck.isOlderThan(minimumVersion)) { + // Older than supported + throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +index 09e87552159e24603aa9a4f658ab4449d7eaeb0a..28ff4859ce2591e206013f4b0f116f3cfd024a3b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +@@ -66,6 +66,13 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { + this.handle = worldAccess; + } + ++ // Folia start - region threading ++ @Override ++ public net.minecraft.world.level.StructureManager structureManager() { ++ return this.handle.structureManager(); ++ } ++ // Folia end - region threading ++ + public WorldGenLevel getHandle() { + return this.handle; + } +@@ -812,4 +819,3 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { + } + // Paper end + } +- +diff --git a/src/main/java/org/spigotmc/SpigotCommand.java b/src/main/java/org/spigotmc/SpigotCommand.java +index 1b60abf5f5951288f6d54f522621472673eada6e..4ea06cb7a9e9db0d7feb0981de90015320c092d4 100644 +--- a/src/main/java/org/spigotmc/SpigotCommand.java ++++ b/src/main/java/org/spigotmc/SpigotCommand.java +@@ -35,6 +35,7 @@ public class SpigotCommand extends Command { + .build() + ); + ++ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading + MinecraftServer console = MinecraftServer.getServer(); + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); + for (ServerLevel world : console.getAllLevels()) { +@@ -43,6 +44,7 @@ public class SpigotCommand extends Command { + console.server.reloadCount++; + + Command.broadcastCommandMessage(sender, text("Reload complete.", NamedTextColor.GREEN)); ++ }); // Folia - region threading + } + + return true; +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index e0d4222a99f22d7130d95cf29b034a98f2f3b76e..48432a7c9df33bae8aa72991843ed61545c64814 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -182,7 +182,7 @@ public class SpigotConfig { + SpigotConfig.restartOnCrash = SpigotConfig.getBoolean("settings.restart-on-crash", SpigotConfig.restartOnCrash); + SpigotConfig.restartScript = SpigotConfig.getString("settings.restart-script", SpigotConfig.restartScript); + SpigotConfig.restartMessage = SpigotConfig.transform(SpigotConfig.getString("messages.restart", "Server is restarting")); +- SpigotConfig.commands.put("restart", new RestartCommand("restart")); ++ //SpigotConfig.commands.put("restart", new RestartCommand("restart")); // Folia - region threading + } + + public static boolean bungee; +@@ -228,7 +228,7 @@ public class SpigotConfig { + } + + private static void tpsCommand() { +- SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps")); ++ //SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps")); // Folia - region threading + } + + public static int playerSample; +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 89e2adbc1e1a0709d03e151e3ffcdbff10a44098..3476d639141c15ddb96fe0da1f11569e1e4b5bec 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -401,7 +401,7 @@ public class SpigotWorldConfig { + this.otherMultiplier = (float) this.getDouble("hunger.other-multiplier", 0.0); + } + +- public int currentPrimedTnt = 0; ++ //public int currentPrimedTnt = 0; // Folia - region threading - moved to regionised world data + public int maxTntTicksPerTick; + private void maxTntPerTick() { + if (SpigotConfig.version < 7) { +diff --git a/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java +index ba271c35eb2804f94cfc893bf94affb9ae13d3ba..db9285c2ff0c805f5d9564b6e8520c33ea5bb65a 100644 +--- a/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java ++++ b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java +@@ -20,6 +20,13 @@ public class TestPluginMeta implements PluginMeta { + this.identifier = identifier; + } + ++ // Folia start - region threading ++ @Override ++ public boolean isFoliaSupported() { ++ return true; ++ } ++ // Folia end - region threading ++ + @Override + public @NotNull String getName() { + return this.identifier; diff --git a/folia-server/paper-patches/features/0001-Update-Logo.patch b/folia-server/paper-patches/features/0002-Update-Logo.patch similarity index 100% rename from folia-server/paper-patches/features/0001-Update-Logo.patch rename to folia-server/paper-patches/features/0002-Update-Logo.patch diff --git a/folia-server/paper-patches/features/0002-Build-changes.patch b/folia-server/paper-patches/features/0003-Build-changes.patch similarity index 100% rename from folia-server/paper-patches/features/0002-Build-changes.patch rename to folia-server/paper-patches/features/0003-Build-changes.patch diff --git a/folia-server/paper-patches/features/0003-Fix-tests-by-removing-them.patch b/folia-server/paper-patches/features/0004-Fix-tests-by-removing-them.patch similarity index 100% rename from folia-server/paper-patches/features/0003-Fix-tests-by-removing-them.patch rename to folia-server/paper-patches/features/0004-Fix-tests-by-removing-them.patch diff --git a/folia-server/paper-patches/features/0004-Region-profiler.patch b/folia-server/paper-patches/features/0005-Region-profiler.patch similarity index 100% rename from folia-server/paper-patches/features/0004-Region-profiler.patch rename to folia-server/paper-patches/features/0005-Region-profiler.patch diff --git a/folia-server/paper-patches/features/0005-Add-watchdog-thread.patch b/folia-server/paper-patches/features/0006-Add-watchdog-thread.patch similarity index 100% rename from folia-server/paper-patches/features/0005-Add-watchdog-thread.patch rename to folia-server/paper-patches/features/0006-Add-watchdog-thread.patch diff --git a/folia-server/paper-patches/features/0006-Add-TPS-From-Region.patch b/folia-server/paper-patches/features/0007-Add-TPS-From-Region.patch similarity index 100% rename from folia-server/paper-patches/features/0006-Add-TPS-From-Region.patch rename to folia-server/paper-patches/features/0007-Add-TPS-From-Region.patch diff --git a/folia-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java.patch b/folia-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java.patch deleted file mode 100644 index 7f07e96..0000000 --- a/folia-server/paper-patches/files/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java.patch +++ /dev/null @@ -1,212 +0,0 @@ ---- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -@@ -1,5 +_,11 @@ - package ca.spottedleaf.moonrise.common.util; - -+import io.papermc.paper.threadedregions.RegionShutdownThread; -+import io.papermc.paper.threadedregions.RegionizedServer; -+import io.papermc.paper.threadedregions.RegionizedWorldData; -+import io.papermc.paper.threadedregions.ThreadedRegionizer; -+import io.papermc.paper.threadedregions.TickRegionScheduler; -+import io.papermc.paper.threadedregions.TickRegions; - import net.minecraft.core.BlockPos; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.level.ChunkPos; -@@ -15,8 +_,26 @@ - - private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); - -+ private static String getRegionInfo(final ThreadedRegionizer.ThreadedRegion region) { -+ if (region == null) { -+ return "{null}"; -+ } -+ -+ final ChunkPos center = region.getCenterChunk(); -+ final net.minecraft.server.level.ServerLevel world = region.regioniser.world; -+ -+ return "{center=" + center + ",world=" + (world == null ? "null" : WorldUtil.getWorldName(world)) + "}"; -+ } -+ - private static String getThreadContext() { -- return "thread=" + Thread.currentThread().getName(); -+ final Thread thread = Thread.currentThread(); -+ -+ if (!(thread instanceof TickThread)) { -+ return "[thread=" + thread + ",class=" + thread.getClass().getName() + "]"; -+ } -+ -+ return "[thread=" + thread.getName() + ",class=" + thread.getClass().getName() + ",region=" + getRegionInfo(TickRegionScheduler.getCurrentRegion()) + "]"; -+ - } - - /** -@@ -123,50 +_,157 @@ - } - - public static boolean isShutdownThread() { -- return false; -+ return Thread.currentThread().getClass() == RegionShutdownThread.class; - } - - public static boolean isTickThreadFor(final Level world, final BlockPos pos) { -- return isTickThread(); -+ return isTickThreadFor(world, pos.getX() >> 4, pos.getZ() >> 4); - } - - public static boolean isTickThreadFor(final Level world, final BlockPos pos, final int blockRadius) { -- return isTickThread(); -+ return isTickThreadFor( -+ world, -+ (pos.getX() - blockRadius) >> 4, (pos.getZ() - blockRadius) >> 4, -+ (pos.getX() + blockRadius) >> 4, (pos.getZ() + blockRadius) >> 4 -+ ); - } - - public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { -- return isTickThread(); -+ return isTickThreadFor(world, pos.x, pos.z); - } - - public static boolean isTickThreadFor(final Level world, final Vec3 pos) { -- return isTickThread(); -+ return isTickThreadFor(world, net.minecraft.util.Mth.floor(pos.x) >> 4, net.minecraft.util.Mth.floor(pos.z) >> 4); - } - - public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { -- return isTickThread(); -+ final ThreadedRegionizer.ThreadedRegion region = -+ TickRegionScheduler.getCurrentRegion(); -+ if (region == null) { -+ return isShutdownThread(); -+ } -+ return ((net.minecraft.server.level.ServerLevel)world).regioniser.getRegionAtUnsynchronised(chunkX, chunkZ) == region; - } - - public static boolean isTickThreadFor(final Level world, final AABB aabb) { -- return isTickThread(); -+ return isTickThreadFor( -+ world, -+ CoordinateUtils.getChunkCoordinate(aabb.minX), CoordinateUtils.getChunkCoordinate(aabb.minZ), -+ CoordinateUtils.getChunkCoordinate(aabb.maxX), CoordinateUtils.getChunkCoordinate(aabb.maxZ) -+ ); - } - - public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { -- return isTickThread(); -+ return isTickThreadFor(world, CoordinateUtils.getChunkCoordinate(blockX), CoordinateUtils.getChunkCoordinate(blockZ)); - } - - public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { -- return isTickThread(); -+ final int fromChunkX = CoordinateUtils.getChunkX(position); -+ final int fromChunkZ = CoordinateUtils.getChunkZ(position); -+ -+ final int toChunkX = CoordinateUtils.getChunkCoordinate(position.x + deltaMovement.x); -+ final int toChunkZ = CoordinateUtils.getChunkCoordinate(position.z + deltaMovement.z); -+ -+ // expect from < to, but that may not be the case -+ return isTickThreadFor( -+ world, -+ Math.min(fromChunkX, toChunkX) - buffer, -+ Math.min(fromChunkZ, toChunkZ) - buffer, -+ Math.max(fromChunkX, toChunkX) + buffer, -+ Math.max(fromChunkZ, toChunkZ) + buffer -+ ); - } - - public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { -- return isTickThread(); -+ final ThreadedRegionizer.ThreadedRegion region = -+ TickRegionScheduler.getCurrentRegion(); -+ if (region == null) { -+ return isShutdownThread(); -+ } -+ -+ final int shift = ((net.minecraft.server.level.ServerLevel)world).regioniser.sectionChunkShift; -+ -+ final int minSectionX = fromChunkX >> shift; -+ final int maxSectionX = toChunkX >> shift; -+ final int minSectionZ = fromChunkZ >> shift; -+ final int maxSectionZ = toChunkZ >> shift; -+ -+ for (int secZ = minSectionZ; secZ <= maxSectionZ; ++secZ) { -+ for (int secX = minSectionX; secX <= maxSectionX; ++secX) { -+ final int lowerLeftCX = secX << shift; -+ final int lowerLeftCZ = secZ << shift; -+ if (((net.minecraft.server.level.ServerLevel)world).regioniser.getRegionAtUnsynchronised(lowerLeftCX, lowerLeftCZ) != region) { -+ return false; -+ } -+ } -+ } -+ -+ return true; - } - - public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { -- return isTickThread(); -+ return isTickThreadFor(world, chunkX - radius, chunkZ - radius, chunkX + radius, chunkZ + radius); - } - - public static boolean isTickThreadFor(final Entity entity) { -- return isTickThread(); -+ if (entity == null) { -+ return true; -+ } -+ final ThreadedRegionizer.ThreadedRegion region = -+ TickRegionScheduler.getCurrentRegion(); -+ if (region == null) { -+ if (RegionizedServer.isGlobalTickThread()) { -+ if (entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { -+ final net.minecraft.server.network.ServerGamePacketListenerImpl possibleBad = serverPlayer.connection; -+ if (possibleBad == null) { -+ return true; -+ } -+ -+ final net.minecraft.network.PacketListener packetListener = possibleBad.connection.getPacketListener(); -+ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl gamePacketListener) { -+ return gamePacketListener.waitingForSwitchToConfig; -+ } -+ if (packetListener instanceof net.minecraft.server.network.ServerConfigurationPacketListenerImpl configurationPacketListener) { -+ return !configurationPacketListener.switchToMain; -+ } -+ return true; -+ } else { -+ return false; -+ } -+ } -+ if (isShutdownThread()) { -+ return true; -+ } -+ if (entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { -+ // off-main access to server player is never ok, server player is owned by one of global context or region context always -+ return false; -+ } -+ // only own entities that have not yet been added to the world -+ -+ // if the entity is removed, then it was in the world previously - which means that a region containing its location -+ // owns it -+ // if the entity has a callback, then it is contained in a world -+ return entity.hasNullCallback() && !entity.isRemoved(); -+ } -+ -+ final Level world = entity.level(); -+ if (world != region.regioniser.world) { -+ // world mismatch -+ return false; -+ } -+ -+ final RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); -+ -+ // pass through the check if the entity is removed and we own its chunk -+ if (worldData.hasEntity(entity)) { -+ return true; -+ } -+ -+ if (entity instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { -+ net.minecraft.server.network.ServerGamePacketListenerImpl conn = serverPlayer.connection; -+ return conn != null && worldData.connections.contains(conn.connection); -+ } else { -+ return ((entity.hasNullCallback() || entity.isRemoved())) && isTickThreadFor((net.minecraft.server.level.ServerLevel)world, entity.chunkPosition()); -+ } - } - } diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/SparksFly.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/SparksFly.java.patch deleted file mode 100644 index 47ae398..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/SparksFly.java.patch +++ /dev/null @@ -1,57 +0,0 @@ ---- a/src/main/java/io/papermc/paper/SparksFly.java -+++ b/src/main/java/io/papermc/paper/SparksFly.java -@@ -33,13 +_,13 @@ - - private final Logger logger; - private final PaperSparkModule spark; -- private final ConcurrentLinkedQueue mainThreadTaskQueue; -+ // Folia - region threading - - private boolean enabled; - private boolean disabledInConfigurationWarningLogged; - - public SparksFly(final Server server) { -- this.mainThreadTaskQueue = new ConcurrentLinkedQueue<>(); -+ // Folia - region threading - this.logger = Logger.getLogger(ID); - this.logger.log(Level.INFO, "This server bundles the spark profiler. For more information please visit https://docs.papermc.io/paper/profiling"); - this.spark = PaperSparkModule.create(Compatibility.VERSION_1_0, server, this.logger, new PaperScheduler() { -@@ -50,7 +_,7 @@ - - @Override - public void executeSync(final Runnable runnable) { -- SparksFly.this.mainThreadTaskQueue.offer(this.catching(runnable, "synchronous")); -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(this.catching(runnable, "synchronous")); // Folia - region threading - } - - private Runnable catching(final Runnable runnable, final String type) { -@@ -88,10 +_,7 @@ - } - - public void executeMainThreadTasks() { -- Runnable task; -- while ((task = this.mainThreadTaskQueue.poll()) != null) { -- task.run(); -- } -+ throw new UnsupportedOperationException(); // Folia - region threading - } - - public void enableEarlyIfRequested() { -@@ -119,7 +_,7 @@ - - private void enable() { - if (!this.enabled) { -- if (GlobalConfiguration.get().spark.enabled) { -+ if (false) { // Folia - disable in-built spark profiler - this.enabled = true; - this.spark.enable(); - } else { -@@ -171,7 +_,7 @@ - } - - public static boolean isPluginPreferred() { -- return Boolean.getBoolean(PREFER_SPARK_PLUGIN_PROPERTY); -+ return true; // Folia - disable in-built spark profiler - } - - private static boolean isPluginEnabled(final Server server) { diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/adventure/ChatProcessor.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/adventure/ChatProcessor.java.patch deleted file mode 100644 index 712e9b1..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/adventure/ChatProcessor.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java -+++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java -@@ -83,7 +_,7 @@ - final CraftPlayer player = this.player.getBukkitEntity(); - final AsyncPlayerChatEvent ae = new AsyncPlayerChatEvent(this.async, player, this.craftbukkit$originalMessage, new LazyPlayerSet(this.server)); - this.post(ae); -- if (listenersOnSyncEvent) { -+ if (false && listenersOnSyncEvent) { // Folia - region threading - final PlayerChatEvent se = new PlayerChatEvent(player, ae.getMessage(), ae.getFormat(), ae.getRecipients()); - se.setCancelled(ae.isCancelled()); // propagate cancelled state - this.queueIfAsyncOrRunImmediately(new Waitable() { -@@ -150,7 +_,7 @@ - ae.setCancelled(cancelled); // propagate cancelled state - this.post(ae); - final boolean listenersOnSyncEvent = canYouHearMe(ChatEvent.getHandlerList()); -- if (listenersOnSyncEvent) { -+ if (false && listenersOnSyncEvent) { // Folia - region threading - this.queueIfAsyncOrRunImmediately(new Waitable() { - @Override - protected Void evaluate() { diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java.patch deleted file mode 100644 index 9aa9565..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java.patch +++ /dev/null @@ -1,57 +0,0 @@ ---- a/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java -+++ b/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java -@@ -23,35 +_,42 @@ - - public static final class CallbackManager { - -- private final Map callbacks = new HashMap<>(); -- private final Queue queue = new ConcurrentLinkedQueue<>(); -+ private final java.util.concurrent.ConcurrentHashMap callbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - region threading -+ // Folia - region threading - - private CallbackManager() { - } - - public UUID addCallback(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { - final UUID id = UUID.randomUUID(); -- this.queue.add(new StoredCallback(callback, options, id)); -+ final StoredCallback scb = new StoredCallback(callback, options, id); // Folia - region threading -+ this.callbacks.put(scb.id(), scb); // Folia - region threading - return id; - } - - public void handleQueue(final int currentTick) { - // Evict expired entries - if (currentTick % 100 == 0) { -- this.callbacks.values().removeIf(callback -> !callback.valid()); -+ this.callbacks.values().removeIf(StoredCallback::expired); // Folia - region threading - don't read uses field - } - -- // Add entries from queue -- StoredCallback callback; -- while ((callback = this.queue.poll()) != null) { -- this.callbacks.put(callback.id(), callback); -- } -+ // Folia - region threading - } - - public void runCallback(final @NotNull Audience audience, final UUID id) { -- final StoredCallback callback = this.callbacks.get(id); -- if (callback != null && callback.valid()) { //TODO Message if expired/invalid? -- callback.takeUse(); -+ // Folia start - region threading -+ final StoredCallback[] use = new StoredCallback[1]; -+ this.callbacks.computeIfPresent(id, (final UUID keyInMap, final StoredCallback value) -> { -+ if (!value.valid()) { -+ return null; -+ } -+ use[0] = value; -+ value.takeUse(); -+ return value.valid() ? value : null; -+ }); -+ final StoredCallback callback = use[0]; -+ if (callback != null) { //TODO Message if expired/invalid? -+ // Folia end - region threading - callback.callback.accept(audience); - } - } diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/PaperCommands.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/PaperCommands.java.patch deleted file mode 100644 index 1cf6178..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/PaperCommands.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/src/main/java/io/papermc/paper/command/PaperCommands.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommands.java -@@ -18,7 +_,7 @@ - static { - COMMANDS.put("paper", new PaperCommand("paper")); - COMMANDS.put("callback", new CallbackCommand("callback")); -- COMMANDS.put("mspt", new MSPTCommand("mspt")); -+ COMMANDS.put("tps", new io.papermc.paper.threadedregions.commands.CommandServerHealth()); // Folia - region threading - } - - public static void registerCommands(final MinecraftServer server) { diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java.patch deleted file mode 100644 index 61df3e3..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -+++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -@@ -129,7 +_,7 @@ - final int z = (e.getKey().z << 4) + 8; - final Component message = text(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)")) - .hoverEvent(HoverEvent.showText(text("Click to teleport to chunk", GREEN))) -- .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey() + " run tp " + x + " " + (world.getWorld().getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING) + 1) + " " + z)); -+ .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey() + " run tp " + x + " " + (128) + " " + z)); // Folia - region threading - avoid sync load here - sender.sendMessage(message); - }); - } else { diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java.patch deleted file mode 100644 index 57b1571..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- a/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java -+++ b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java -@@ -18,7 +_,9 @@ - public final class HeapDumpCommand implements PaperSubcommand { - @Override - public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading - this.dumpHeap(sender); -+ }); // Folia - region threading - return true; - } - diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java.patch deleted file mode 100644 index 04acff5..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- a/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java -+++ b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java -@@ -16,7 +_,9 @@ - public final class ReloadCommand implements PaperSubcommand { - @Override - public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading - this.doReload(sender); -+ }); // Folia - region threading - return true; - } - diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java.patch deleted file mode 100644 index eae8c72..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -398,4 +_,17 @@ - } - } - } -+ // Folia start - threaded regions -+ public ThreadedRegions threadedRegions; -+ public class ThreadedRegions extends ConfigurationPart { -+ -+ public int threads = -1; -+ public int gridExponent = 4; -+ -+ @PostProcess -+ public void postProcess() { -+ io.papermc.paper.threadedregions.TickRegions.init(this); -+ } -+ } -+ // Folia end - threaded regions - } diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java.patch deleted file mode 100644 index 9eefbc0..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -@@ -493,6 +_,14 @@ - public Chunks chunks; - - public class Chunks extends ConfigurationPart { -+ -+ // Folia start - region threading - force prevent moving into unloaded chunks -+ @PostProcess -+ public void postProcess() { -+ this.preventMovingIntoUnloadedChunks = true; -+ } -+ // Folia end - region threading - force prevent moving into unloaded chunks -+ - public AutosavePeriod autoSaveInterval = AutosavePeriod.def(); - public int maxAutoSaveChunksPerTick = 24; - public int fixedChunkInhabitedTime = -1; diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java.patch deleted file mode 100644 index c680038..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java -+++ b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java -@@ -11,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public AbstractSchoolingFish getHandleRaw() { -+ return (AbstractSchoolingFish)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractSchoolingFish getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractSchoolingFish) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/entity/activation/ActivationType.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/entity/activation/ActivationType.java.patch deleted file mode 100644 index 69d0bf9..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/entity/activation/ActivationType.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/src/main/java/io/papermc/paper/entity/activation/ActivationType.java -+++ b/src/main/java/io/papermc/paper/entity/activation/ActivationType.java -@@ -20,7 +_,7 @@ - RAIDER, - MISC; - -- AABB boundingBox = new AABB(0, 0, 0, 0, 0, 0); -+ //AABB boundingBox = new AABB(0, 0, 0, 0, 0, 0); // Folia - threaded regions - replaced by local variable - - /** - * Returns the activation type for the given entity. diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java.patch deleted file mode 100644 index b0edbe2..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java.patch +++ /dev/null @@ -1,177 +0,0 @@ ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java -@@ -32,7 +_,9 @@ - @Override - @Nullable - public Permission getPermission(@NotNull String name) { -+ synchronized (this) { // Folia - synchronized - return this.permissions().get(name.toLowerCase(java.util.Locale.ENGLISH)); -+ } // Folia - synchronized - } - - @Override -@@ -52,12 +_,24 @@ - private void addPermission(@NotNull Permission perm, boolean dirty) { - String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); - -+ Boolean recalc; // Folia - synchronized -+ synchronized (this) { // Folia - synchronized - if (this.permissions().containsKey(name)) { - throw new IllegalArgumentException("The permission " + name + " is already defined!"); - } - - this.permissions().put(name, perm); -- this.calculatePermissionDefault(perm, dirty); -+ recalc = this.calculatePermissionDefault(perm, dirty); -+ } // Folia - synchronized -+ // Folia start - synchronize this class - we hold a lock now, prevent deadlock by moving this out -+ if (recalc != null) { -+ if (recalc.booleanValue()) { -+ this.dirtyPermissibles(true); -+ } else { -+ this.dirtyPermissibles(false); -+ } -+ } -+ // Folia end - synchronize this class - we hold a lock now, prevent deadlock by moving this out - } - - @Override -@@ -80,42 +_,58 @@ - - @Override - public void recalculatePermissionDefaults(@NotNull Permission perm) { -+ Boolean recalc = null; // Folia - synchronized -+ synchronized (this) { // Folia - synchronized - // we need a null check here because some plugins for some unknown reason pass null into this? - if (perm != null && this.permissions().containsKey(perm.getName().toLowerCase(Locale.ROOT))) { - this.defaultPerms().get(true).remove(perm); - this.defaultPerms().get(false).remove(perm); - -- this.calculatePermissionDefault(perm, true); -- } -+ recalc = this.calculatePermissionDefault(perm, true); // Folia - synchronized -+ } -+ } // Folia - synchronized -+ // Folia start - synchronize this class - we hold a lock now, prevent deadlock by moving this out -+ if (recalc != null) { -+ if (recalc.booleanValue()) { -+ this.dirtyPermissibles(true); -+ } else { -+ this.dirtyPermissibles(false); -+ } -+ } -+ // Folia end - synchronize this class - we hold a lock now, prevent deadlock by moving this out - } - -- private void calculatePermissionDefault(@NotNull Permission perm, boolean dirty) { -+ private Boolean calculatePermissionDefault(@NotNull Permission perm, boolean dirty) { // Folia - synchronize this class - if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) { - this.defaultPerms().get(true).add(perm); - if (dirty) { -- this.dirtyPermissibles(true); -+ return Boolean.TRUE; // Folia - synchronize this class - we hold a lock now, prevent deadlock by moving this out - } - } - if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) { - this.defaultPerms().get(false).add(perm); - if (dirty) { -- this.dirtyPermissibles(false); -+ return Boolean.FALSE; // Folia - synchronize this class - we hold a lock now, prevent deadlock by moving this out - } - } -+ return null; // Folia - synchronize this class - } - - - @Override - public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) { -+ synchronized (this) { // Folia - synchronized - String name = permission.toLowerCase(java.util.Locale.ENGLISH); - Map map = this.permSubs().computeIfAbsent(name, k -> new WeakHashMap<>()); - - map.put(permissible, true); -+ } // Folia - synchronized - } - - @Override - public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) { - String name = permission.toLowerCase(java.util.Locale.ENGLISH); -+ synchronized (this) { // Folia - synchronized - Map map = this.permSubs().get(name); - - if (map != null) { -@@ -125,11 +_,13 @@ - this.permSubs().remove(name); - } - } -+ } // Folia - synchronized - } - - @Override - @NotNull - public Set getPermissionSubscriptions(@NotNull String permission) { -+ synchronized (this) { // Folia - synchronized - String name = permission.toLowerCase(java.util.Locale.ENGLISH); - Map map = this.permSubs().get(name); - -@@ -138,17 +_,21 @@ - } else { - return ImmutableSet.copyOf(map.keySet()); - } -+ } // Folia - synchronized - } - - @Override - public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) { -+ synchronized (this) { // Folia - synchronized - Map map = this.defSubs().computeIfAbsent(op, k -> new WeakHashMap<>()); - - map.put(permissible, true); -+ } // Folia - synchronized - } - - @Override - public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) { -+ synchronized (this) { // Folia - synchronized - Map map = this.defSubs().get(op); - - if (map != null) { -@@ -158,11 +_,13 @@ - this.defSubs().remove(op); - } - } -+ } // Folia - synchronized - } - - @Override - @NotNull - public Set getDefaultPermSubscriptions(boolean op) { -+ synchronized (this) { // Folia - synchronized - Map map = this.defSubs().get(op); - - if (map == null) { -@@ -170,19 +_,24 @@ - } else { - return ImmutableSet.copyOf(map.keySet()); - } -+ } // Folia - synchronized - } - - @Override - @NotNull - public Set getPermissions() { -+ synchronized (this) { // Folia - synchronized - return new HashSet<>(this.permissions().values()); -+ } // Folia - synchronized - } - - @Override - public void clearPermissions() { -+ synchronized (this) { // Folia - synchronized - this.permissions().clear(); - this.defaultPerms().get(true).clear(); - this.defaultPerms().get(false).clear(); -+ } // Folia - synchronized - } - - diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java.patch deleted file mode 100644 index 0aba61f..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java.patch +++ /dev/null @@ -1,16 +0,0 @@ ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -@@ -256,12 +_,7 @@ - + pluginName + " (Is it up to date?)", ex, plugin); // Paper - } - -- try { -- this.server.getScheduler().cancelTasks(plugin); -- } catch (Throwable ex) { -- this.handlePluginException("Error occurred (in the plugin loader) while cancelling tasks for " -- + pluginName + " (Is it up to date?)", ex, plugin); // Paper -- } -+ // Folia - region threading - - // Paper start - Folia schedulers - try { diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java.patch deleted file mode 100644 index 37ea36b..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java -+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java -@@ -64,6 +_,7 @@ - private PermissionConfiguration permissionConfiguration = new PermissionConfiguration(PermissionDefault.OP, List.of()); - @Required - private ApiVersion apiVersion; -+ private boolean foliaSupported = false; // Folia - - private Map> dependencies = new EnumMap<>(PluginDependencyLifeCycle.class); - -@@ -250,6 +_,13 @@ - public @NotNull String getAPIVersion() { - return this.apiVersion.getVersionString(); - } -+ -+ // Folia start -+ @Override -+ public boolean isFoliaSupported() { -+ return this.foliaSupported; -+ } -+ // Folia end - - @Override - public @NotNull List getProvidedPlugins() { diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java.patch deleted file mode 100644 index 7d64071..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java.patch +++ /dev/null @@ -1,14 +0,0 @@ ---- a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java -+++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java -@@ -24,6 +_,11 @@ - - @Override - public PaperPluginParent build(JarFile file, PaperPluginMeta configuration, Path source) { -+ // Folia start - block plugins not marked as supported -+ if (!configuration.isFoliaSupported()) { -+ throw new RuntimeException("Could not load plugin '" + configuration.getDisplayName() + "' as it is not marked as supporting Folia!"); -+ } -+ // Folia end - block plugins not marked as supported - Logger jul = PaperPluginLogger.getLogger(configuration); - ComponentLogger logger = ComponentLogger.logger(jul.getName()); - PluginProviderContext context = PluginProviderContextImpl.create(configuration, logger, source); diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java.patch deleted file mode 100644 index 72bcfb0..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java -+++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java -@@ -35,6 +_,11 @@ - - @Override - public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException { -+ // Folia start - block plugins not marked as supported -+ if (!configuration.isFoliaSupported()) { -+ throw new RuntimeException("Could not load plugin '" + configuration.getDisplayName() + "' as it is not marked as supporting Folia!"); -+ } -+ // Folia end - block plugins not marked as supported - // Copied from SimplePluginManager#loadPlugins - // Spigot doesn't validate the name when the config is created, and instead when the plugin is loaded. - // Paper plugin configuration will do these checks in config serializer instead of when this is created. -@@ -81,4 +_,3 @@ - return descriptionFile; - } - } -- diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java.patch deleted file mode 100644 index 00b3679..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -+++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -@@ -50,6 +_,14 @@ - this.entity = Validate.notNull(entity); - } - -+ // Folia start - region threading -+ public boolean isRetired() { -+ synchronized (this.stateLock) { -+ return this.tickCount == RETIRED_TICK_COUNT; -+ } -+ } -+ // Folia end - region threading -+ - /** - * Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback - * on all currently scheduled tasks. diff --git a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/util/MCUtil.java.patch b/folia-server/paper-patches/files/src/main/java/io/papermc/paper/util/MCUtil.java.patch deleted file mode 100644 index 89d2d8c..0000000 --- a/folia-server/paper-patches/files/src/main/java/io/papermc/paper/util/MCUtil.java.patch +++ /dev/null @@ -1,41 +0,0 @@ ---- a/src/main/java/io/papermc/paper/util/MCUtil.java -+++ b/src/main/java/io/papermc/paper/util/MCUtil.java -@@ -94,6 +_,7 @@ - */ - public static void ensureMain(String reason, Runnable run) { - if (!isMainThread()) { -+ if (true) throw new UnsupportedOperationException(); // Folia - region threading - if (reason != null) { - MinecraftServer.LOGGER.warn("Asynchronous " + reason + "!", new IllegalStateException()); - } -@@ -147,6 +_,30 @@ - public static Location toLocation(Level world, BlockPos pos) { - return new Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()); - } -+ -+ // Folia start - TODO MERGE INTO MCUTIL -+ /** -+ * Converts a NMS World/Vector to Bukkit Location -+ * @param world -+ * @param pos -+ * @return -+ */ -+ public static Location toLocation(Level world, Vec3 pos) { -+ return new Location(world.getWorld(), pos.x(), pos.y(), pos.z()); -+ } -+ -+ /** -+ * Converts a NMS World/Vector to Bukkit Location -+ * @param world -+ * @param pos -+ * @param yaw -+ * @param pitch -+ * @return -+ */ -+ public static Location toLocation(Level world, Vec3 pos, float yaw, float pitch) { -+ return new Location(world.getWorld(), pos.x(), pos.y(), pos.z(), yaw, pitch); -+ } -+ // Folia end - TODO MERGE INTO MCUTIL - - public static BlockPos toBlockPosition(Location loc) { - return new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch deleted file mode 100644 index 53afab3..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch +++ /dev/null @@ -1,133 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -317,7 +_,7 @@ - public final io.papermc.paper.SparksFly spark; // Paper - spark - - // Paper start - Folia region threading API -- private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler(); -+ private final io.papermc.paper.threadedregions.scheduler.FoliaRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaRegionScheduler(); // Folia - region threading - private final io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler asyncScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler(); - private final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler globalRegionScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler(); - -@@ -394,7 +_,7 @@ - - @Override - public final boolean isGlobalTickThread() { -- return ca.spottedleaf.moonrise.common.util.TickThread.isTickThread(); -+ return io.papermc.paper.threadedregions.RegionizedServer.isGlobalTickThread(); // Folia - region threading API - } - // Paper end - Folia reagion threading API - -@@ -989,6 +_,9 @@ - - // NOTE: Should only be called from DedicatedServer.ah() - public boolean dispatchServerCommand(CommandSender sender, ConsoleInput serverCommand) { -+ // Folia start - region threading -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("May not dispatch server commands async"); -+ // Folia end - region threading - if (sender instanceof Conversable) { - Conversable conversable = (Conversable) sender; - -@@ -1008,12 +_,46 @@ - } - } - -+ // Folia start - region threading -+ public void dispatchCmdAsync(CommandSender sender, String commandLine) { -+ if ((sender instanceof Entity entity)) { -+ ((org.bukkit.craftbukkit.entity.CraftEntity)entity).taskScheduler.schedule( -+ (nmsEntity) -> { -+ CraftServer.this.dispatchCommand(nmsEntity.getBukkitEntity(), commandLine); -+ }, -+ null, -+ 1L -+ ); -+ } else if (sender instanceof ConsoleCommandSender || sender instanceof io.papermc.paper.commands.FeedbackForwardingSender) { -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { -+ CraftServer.this.dispatchCommand(sender, commandLine); -+ }); -+ } else { -+ // huh? -+ throw new UnsupportedOperationException("Dispatching command for " + sender); -+ } -+ } -+ // Folia end - region threading -+ - @Override - public boolean dispatchCommand(CommandSender sender, String commandLine) { - Preconditions.checkArgument(sender != null, "sender cannot be null"); - Preconditions.checkArgument(commandLine != null, "commandLine cannot be null"); - org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + commandLine); // Spigot // Paper - Include command in error message - -+ // Folia start - region threading -+ if ((sender instanceof Entity entity)) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(((org.bukkit.craftbukkit.entity.CraftEntity)entity).getHandle(), "Dispatching command async"); -+ } else if (sender instanceof ConsoleCommandSender || sender instanceof net.minecraft.server.rcon.RconConsoleSource -+ || sender instanceof org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender -+ || sender instanceof io.papermc.paper.commands.FeedbackForwardingSender) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Dispatching command async"); -+ } else { -+ // huh? -+ throw new UnsupportedOperationException("Dispatching command for " + sender); -+ } -+ // Folia end - region threading -+ - if (this.commandMap.dispatch(sender, commandLine)) { - return true; - } -@@ -1285,6 +_,7 @@ - - @Override - public World createWorld(WorldCreator creator) { -+ if (true) throw new UnsupportedOperationException(); // Folia - not implemented properly yet - Preconditions.checkState(this.console.getAllLevels().iterator().hasNext(), "Cannot create additional worlds on STARTUP"); - //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot create a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. - Preconditions.checkArgument(creator != null, "WorldCreator cannot be null"); -@@ -1482,6 +_,7 @@ - - @Override - public boolean unloadWorld(World world, boolean save) { -+ if (true) throw new UnsupportedOperationException(); // Folia - not implemented properly yet - //Preconditions.checkState(!this.console.isIteratingOverLevels, "Cannot unload a world while worlds are being ticked"); // Paper - Cat - Temp disable. We'll see how this goes. - if (world == null) { - return false; -@@ -3079,11 +_,27 @@ - - @Override - public double[] getTPS() { -+ // Folia start - region threading -+ ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool.SchedulableTick task = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentTickingTask(); -+ if (task == null) { -+ // might be on the shutdown thread, try retrieving the current region -+ if (io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion() != null) { -+ // we are on the shutdown thread -+ task = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion().getData().getRegionSchedulingHandle(); -+ } -+ } -+ if (!(task instanceof io.papermc.paper.threadedregions.TickRegionScheduler.RegionScheduleHandle tickHandle)) { -+ throw new UnsupportedOperationException("Not on any region"); -+ } -+ -+ // 1m, 5m, 15m -+ long currTime = System.nanoTime(); - return new double[] { -- net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), -- net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), -- net.minecraft.server.MinecraftServer.getServer().tps15.getAverage() -+ tickHandle.getTickReport1m(currTime).tpsData().segmentAll().average(), -+ tickHandle.getTickReport5m(currTime).tpsData().segmentAll().average(), -+ tickHandle.getTickReport15m(currTime).tpsData().segmentAll().average(), - }; -+ // Folia end - region threading - } - - // Paper start - adventure sounds -@@ -3254,7 +_,7 @@ - - @Override - public int getCurrentTick() { -- return net.minecraft.server.MinecraftServer.currentTick; -+ return (int)io.papermc.paper.threadedregions.RegionizedServer.getCurrentTick(); // Folia - region threading - } - - @Override diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch deleted file mode 100644 index 7a9f29b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch +++ /dev/null @@ -1,432 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -227,7 +_,7 @@ - - @Override - public int getTickableTileEntityCount() { -- return world.blockEntityTickers.size(); -+ throw new UnsupportedOperationException(); // Folia - region threading - TODO fix this? - } - - @Override -@@ -294,7 +_,7 @@ - // Paper start - per world spawn limits - for (SpawnCategory spawnCategory : SpawnCategory.values()) { - if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { -- setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); -+ this.spawnCategoryLimit.put(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); // Folia - region threading - } - } - // Paper end - per world spawn limits -@@ -365,6 +_,7 @@ - - @Override - public Chunk getChunkAt(int x, int z) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "Async chunk retrieval"); // Folia - region threading - warnUnsafeChunk("getting a faraway chunk", x, z); // Paper - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); - return new CraftChunk(chunk); -@@ -395,10 +_,10 @@ - @Override - public boolean isChunkGenerated(int x, int z) { - // Paper start - Fix this method -- if (!Bukkit.isPrimaryThread()) { -+ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.getHandle(), x, z)) { // Folia - region threading - return java.util.concurrent.CompletableFuture.supplyAsync(() -> { - return CraftWorld.this.isChunkGenerated(x, z); -- }, world.getChunkSource().mainThreadProcessor).join(); -+ }, (run) -> { io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(this.getHandle(), x, z, run);}).join(); // Folia - region threading - } - ChunkAccess chunk = world.getChunkSource().getChunkAtImmediately(x, z); - if (chunk != null) { -@@ -455,7 +_,7 @@ - } - - private boolean unloadChunk0(int x, int z, boolean save) { -- org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // Folia - region threading - if (!this.isChunkLoaded(x, z)) { - return true; - } -@@ -472,6 +_,7 @@ - - @Override - public boolean refreshChunk(int x, int z) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // Folia - region threading - ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); - if (playerChunk == null) return false; - -@@ -522,7 +_,7 @@ - - @Override - public boolean loadChunk(int x, int z, boolean generate) { -- org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // Folia - region threading - warnUnsafeChunk("loading a faraway chunk", x, z); // Paper - ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper - -@@ -562,7 +_,7 @@ - - final DistanceManager distanceManager = this.world.getChunkSource().chunkMap.distanceManager; - if (distanceManager.addPluginRegionTicket(new ChunkPos(x, z), plugin)) { -- this.getChunkAt(x, z); // ensure it's loaded -+ //this.getChunkAt(x, z); // ensure it's loaded // Folia - region threading - do not load chunks for tickets anymore to make this mt-safe - return true; - } - -@@ -616,21 +_,24 @@ - - @Override - public boolean isChunkForceLoaded(int x, int z) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot read force-loaded chunk off global region"); // Folia - region threading - return this.getHandle().getForcedChunks().contains(ChunkPos.asLong(x, z)); - } - - @Override - public void setChunkForceLoaded(int x, int z, boolean forced) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify force-loaded chunks off global region"); // Folia - region threading - warnUnsafeChunk("forceloading a faraway chunk", x, z); // Paper - this.getHandle().setChunkForced(x, z, forced); - } - - @Override - public Collection getForceLoadedChunks() { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot read force-loaded chunks off global region"); // Folia - region threading - Set chunks = new HashSet<>(); - - for (long coord : this.getHandle().getForcedChunks()) { -- chunks.add(this.getChunkAt(ChunkPos.getX(coord), ChunkPos.getZ(coord))); -+ chunks.add(new org.bukkit.craftbukkit.CraftChunk(this.world, ChunkPos.getX(coord), ChunkPos.getZ(coord))); // Folia - region threading - } - - return Collections.unmodifiableCollection(chunks); -@@ -750,13 +_,15 @@ - - @Override - public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { -- this.world.captureTreeGeneration = true; -- this.world.captureBlockStates = true; -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // Folia - region threading -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - region threading -+ worldData.captureTreeGeneration = true; // Folia - region threading -+ worldData.captureBlockStates = true; // Folia - region threading - boolean grownTree = this.generateTree(loc, type); -- this.world.captureBlockStates = false; -- this.world.captureTreeGeneration = false; -+ worldData.captureBlockStates = false; // Folia - region threading -+ worldData.captureTreeGeneration = false; // Folia - region threading - if (grownTree) { // Copy block data to delegate -- for (BlockState blockstate : this.world.capturedBlockStates.values()) { -+ for (BlockState blockstate : worldData.capturedBlockStates.values()) { // Folia - region threading - BlockPos position = ((CraftBlockState) blockstate).getPosition(); - net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position); - int flag = ((CraftBlockState) blockstate).getFlag(); -@@ -764,10 +_,10 @@ - net.minecraft.world.level.block.state.BlockState newBlock = this.world.getBlockState(position); - this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flag, 512); - } -- this.world.capturedBlockStates.clear(); -+ worldData.capturedBlockStates.clear(); // Folia - region threading - return true; - } else { -- this.world.capturedBlockStates.clear(); -+ worldData.capturedBlockStates.clear(); // Folia - region threading - return false; - } - } -@@ -801,6 +_,7 @@ - - @Override - public void setTime(long time) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify time off of the global region"); // Folia - region threading - long margin = (time - this.getFullTime()) % 24000; - if (margin < 0) margin += 24000; - this.setFullTime(this.getFullTime() + margin); -@@ -813,6 +_,7 @@ - - @Override - public void setFullTime(long time) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify time off of the global region"); // Folia - region threading - // Notify anyone who's listening - TimeSkipEvent event = new TimeSkipEvent(this, TimeSkipEvent.SkipReason.CUSTOM, time - this.world.getDayTime()); - this.server.getPluginManager().callEvent(event); -@@ -840,7 +_,7 @@ - - @Override - public long getGameTime() { -- return this.world.levelData.getGameTime(); -+ return this.getHandle().getGameTime(); // Folia - region threading - } - - @Override -@@ -865,6 +_,7 @@ - } - public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { - // Paper end - expand explosion API -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // Folia - region threading - net.minecraft.world.level.Level.ExplosionInteraction explosionType; - if (!breakBlocks) { - explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks -@@ -874,6 +_,7 @@ - explosionType = net.minecraft.world.level.Level.ExplosionInteraction.MOB; // Respect mobGriefing gamerule - } - -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // Folia - region threading - net.minecraft.world.entity.Entity entity = (source == null) ? null : ((CraftEntity) source).getHandle(); - return !this.world.explode0(entity, Explosion.getDefaultDamageSource(this.world, entity), null, x, y, z, power, setFire, explosionType, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE, configurator).wasCanceled; // Paper - expand explosion API - } -@@ -956,6 +_,7 @@ - - @Override - public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // Folia - region threading - warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper - // Transient load for this tick - return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); -@@ -986,6 +_,7 @@ - @Override - public void setBiome(int x, int y, int z, Holder bb) { - BlockPos pos = new BlockPos(x, 0, z); -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // Folia - region threading - if (this.world.hasChunkAt(pos)) { - net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); - -@@ -1316,6 +_,7 @@ - - @Override - public void setStorm(boolean hasStorm) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading - this.world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents - this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) - this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) -@@ -1328,6 +_,7 @@ - - @Override - public void setWeatherDuration(int duration) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading - this.world.serverLevelData.setRainTime(duration); - } - -@@ -1338,6 +_,7 @@ - - @Override - public void setThundering(boolean thundering) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading - this.world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents - this.setThunderDuration(0); // Reset weather duration (legacy behaviour) - this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) -@@ -1350,6 +_,7 @@ - - @Override - public void setThunderDuration(int duration) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading - this.world.serverLevelData.setThunderTime(duration); - } - -@@ -1360,6 +_,7 @@ - - @Override - public void setClearWeatherDuration(int duration) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify weather off of the global region"); // Folia - region threading - this.world.serverLevelData.setClearWeatherTime(duration); - } - -@@ -1558,6 +_,7 @@ - - @Override - public void setKeepSpawnInMemory(boolean keepLoaded) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify keep spawn in memory off of the global region"); // Folia - region threading - if (keepLoaded) { - this.setGameRule(GameRule.SPAWN_CHUNK_RADIUS, this.getGameRuleDefault(GameRule.SPAWN_CHUNK_RADIUS)); - } else { -@@ -1626,6 +_,7 @@ - - @Override - public void setHardcore(boolean hardcore) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.world.serverLevelData.settings.hardcore = hardcore; - } - -@@ -1638,6 +_,7 @@ - @Override - @Deprecated - public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setTicksPerSpawns(SpawnCategory.ANIMAL, ticksPerAnimalSpawns); - } - -@@ -1650,6 +_,7 @@ - @Override - @Deprecated - public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setTicksPerSpawns(SpawnCategory.MONSTER, ticksPerMonsterSpawns); - } - -@@ -1662,6 +_,7 @@ - @Override - @Deprecated - public void setTicksPerWaterSpawns(int ticksPerWaterSpawns) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setTicksPerSpawns(SpawnCategory.WATER_ANIMAL, ticksPerWaterSpawns); - } - -@@ -1674,6 +_,7 @@ - @Override - @Deprecated - public void setTicksPerWaterAmbientSpawns(int ticksPerWaterAmbientSpawns) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setTicksPerSpawns(SpawnCategory.WATER_AMBIENT, ticksPerWaterAmbientSpawns); - } - -@@ -1686,6 +_,7 @@ - @Override - @Deprecated - public void setTicksPerWaterUndergroundCreatureSpawns(int ticksPerWaterUndergroundCreatureSpawns) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setTicksPerSpawns(SpawnCategory.WATER_UNDERGROUND_CREATURE, ticksPerWaterUndergroundCreatureSpawns); - } - -@@ -1698,11 +_,13 @@ - @Override - @Deprecated - public void setTicksPerAmbientSpawns(int ticksPerAmbientSpawns) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setTicksPerSpawns(SpawnCategory.AMBIENT, ticksPerAmbientSpawns); - } - - @Override - public void setTicksPerSpawns(SpawnCategory spawnCategory, int ticksPerCategorySpawn) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); - Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); - -@@ -1719,21 +_,25 @@ - - @Override - public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify metadata off of the global region"); // Folia - region threading - this.server.getWorldMetadata().setMetadata(this, metadataKey, newMetadataValue); - } - - @Override - public List getMetadata(String metadataKey) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot retrieve metadata off of the global region"); // Folia - region threading - return this.server.getWorldMetadata().getMetadata(this, metadataKey); - } - - @Override - public boolean hasMetadata(String metadataKey) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot retrieve metadata off of the global region"); // Folia - region threading - return this.server.getWorldMetadata().hasMetadata(this, metadataKey); - } - - @Override - public void removeMetadata(String metadataKey, Plugin owningPlugin) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify metadata off of the global region"); // Folia - region threading - this.server.getWorldMetadata().removeMetadata(this, metadataKey, owningPlugin); - } - -@@ -1746,6 +_,7 @@ - @Override - @Deprecated - public void setMonsterSpawnLimit(int limit) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setSpawnLimit(SpawnCategory.MONSTER, limit); - } - -@@ -1758,6 +_,7 @@ - @Override - @Deprecated - public void setAnimalSpawnLimit(int limit) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setSpawnLimit(SpawnCategory.ANIMAL, limit); - } - -@@ -1770,6 +_,7 @@ - @Override - @Deprecated - public void setWaterAnimalSpawnLimit(int limit) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setSpawnLimit(SpawnCategory.WATER_ANIMAL, limit); - } - -@@ -1782,6 +_,7 @@ - @Override - @Deprecated - public void setWaterAmbientSpawnLimit(int limit) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setSpawnLimit(SpawnCategory.WATER_AMBIENT, limit); - } - -@@ -1794,6 +_,7 @@ - @Override - @Deprecated - public void setWaterUndergroundCreatureSpawnLimit(int limit) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setSpawnLimit(SpawnCategory.WATER_UNDERGROUND_CREATURE, limit); - } - -@@ -1806,6 +_,7 @@ - @Override - @Deprecated - public void setAmbientSpawnLimit(int limit) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - this.setSpawnLimit(SpawnCategory.AMBIENT, limit); - } - -@@ -1828,6 +_,7 @@ - - @Override - public void setSpawnLimit(SpawnCategory spawnCategory, int limit) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); - Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); - -@@ -1910,7 +_,7 @@ - if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - - ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); -- ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); -+ ChunkMap.TrackedEntity entityTracker = ((CraftEntity) entity).getHandle().moonrise$getTrackedEntity(); // Folia - region threading - if (entityTracker != null) { - entityTracker.broadcastAndSend(packet); - } -@@ -1931,7 +_,7 @@ - if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; - - ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(ResourceLocation.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); -- ChunkMap.TrackedEntity entityTracker = this.getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); -+ ChunkMap.TrackedEntity entityTracker = ((CraftEntity)entity).getHandle().moonrise$getTrackedEntity(); // Folia - region threading - if (entityTracker != null) { - entityTracker.broadcastAndSend(packet); - } -@@ -2014,6 +_,7 @@ - - @Override - public boolean setGameRuleValue(String rule, String value) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - // No null values allowed - if (rule == null || value == null) return false; - -@@ -2062,6 +_,7 @@ - - @Override - public boolean setGameRule(GameRule rule, T newValue) { -+ io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify server settings off of the global region"); // Folia - region threading - Preconditions.checkArgument(rule != null, "GameRule cannot be null"); - Preconditions.checkArgument(newValue != null, "GameRule value cannot be null"); - -@@ -2289,6 +_,12 @@ - - @Override - public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { -+ // Folia start - region threading -+ if (sourceEntity != null && !Bukkit.isOwnedByCurrentRegion(sourceEntity)) { -+ throw new IllegalStateException("Cannot send game event asynchronously"); -+ } -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); -+ // Folia end - region threading - getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); - } - // Paper end diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch deleted file mode 100644 index 2ba5ead..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch +++ /dev/null @@ -1,201 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -75,6 +_,11 @@ - } - - public net.minecraft.world.level.block.state.BlockState getNMS() { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - return this.world.getBlockState(this.position); - } - -@@ -157,6 +_,11 @@ - } - - private void setData(final byte data, int flag) { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); - } - -@@ -198,6 +_,11 @@ - } - - public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { -+ // Folia start - region threading -+ if (world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); -+ } -+ // Folia end - region threading - // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup - if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes - // SPIGOT-4612: faster - just clear tile -@@ -343,18 +_,33 @@ - - @Override - public Biome getBiome() { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); - } - - // Paper start - @Override - public Biome getComputedBiome() { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); - } - // Paper end - - @Override - public void setBiome(Biome bio) { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); - } - -@@ -402,6 +_,11 @@ - - @Override - public boolean isBlockFaceIndirectlyPowered(BlockFace face) { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); - - Block relative = this.getRelative(face); -@@ -414,6 +_,11 @@ - - @Override - public int getBlockPower(BlockFace face) { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - int power = 0; - net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); - int x = this.getX(); -@@ -500,6 +_,11 @@ - - @Override - public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - // Paper end - // Order matters here, need to drop before setting to air so skulls can get their data - net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); -@@ -543,21 +_,27 @@ - - @Override - public boolean applyBoneMeal(BlockFace face) { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - Direction direction = CraftBlock.blockFaceToNotch(face); - BlockFertilizeEvent event = null; - ServerLevel world = this.getCraftWorld().getHandle(); - UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false)); - -+ io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - region threading - // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent -- world.captureTreeGeneration = true; -+ worldData.captureTreeGeneration = true; // Folia - region threading - InteractionResult result = BoneMealItem.applyBonemeal(context); -- world.captureTreeGeneration = false; -+ worldData.captureTreeGeneration = false; // Folia - region threading - -- if (world.capturedBlockStates.size() > 0) { -- TreeType treeType = SaplingBlock.treeType; -- SaplingBlock.treeType = null; -- List blocks = new ArrayList<>(world.capturedBlockStates.values()); -- world.capturedBlockStates.clear(); -+ if (worldData.capturedBlockStates.size() > 0) { // Folia - region threading -+ TreeType treeType = SaplingBlock.treeTypeRT.get(); // Folia - region threading -+ SaplingBlock.treeTypeRT.set(null); // Folia - region threading -+ List blocks = new ArrayList<>(worldData.capturedBlockStates.values()); // Folia - region threading -+ worldData.capturedBlockStates.clear(); // Folia - region threading - StructureGrowEvent structureEvent = null; - - if (treeType != null) { -@@ -644,6 +_,11 @@ - - @Override - public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - Preconditions.checkArgument(start != null, "Location start cannot be null"); - Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); - start.checkFinite(); -@@ -685,6 +_,11 @@ - - @Override - public boolean canPlace(BlockData data) { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - Preconditions.checkArgument(data != null, "BlockData cannot be null"); - net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); - net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); -@@ -719,18 +_,32 @@ - - @Override - public void tick() { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - final ServerLevel level = this.world.getMinecraftWorld(); - this.getNMS().tick(level, this.position, level.random); - } - -- - @Override - public void fluidTick() { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - this.getNMSFluid().tick(this.world.getMinecraftWorld(), this.position, this.getNMS()); - } - - @Override - public void randomTick() { -+ // Folia start - region threading -+ if (this.world instanceof ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, this.position, "Cannot read world asynchronously"); -+ } -+ // Folia end - region threading - final ServerLevel level = this.world.getMinecraftWorld(); - this.getNMS().randomTick(level, this.position, level.random); - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java.patch deleted file mode 100644 index ccb6997..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -@@ -25,7 +_,7 @@ - private final T tileEntity; - private final T snapshot; - public boolean snapshotDisabled; // Paper -- public static boolean DISABLE_SNAPSHOT = false; // Paper -+ public static final ThreadLocal DISABLE_SNAPSHOT = ThreadLocal.withInitial(() -> Boolean.FALSE); // Paper // Folia - region threading - - public CraftBlockEntityState(World world, T tileEntity) { - super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); -@@ -34,8 +_,8 @@ - - try { // Paper - Show blockstate location if we failed to read it - // Paper start -- this.snapshotDisabled = DISABLE_SNAPSHOT; -- if (DISABLE_SNAPSHOT) { -+ this.snapshotDisabled = DISABLE_SNAPSHOT.get().booleanValue(); // Folia - region threading -+ if (this.snapshotDisabled) { // Folia - region threading - this.snapshot = this.tileEntity; - } else { - this.snapshot = this.createSnapshot(tileEntity); diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java.patch deleted file mode 100644 index 34b0d7b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java.patch +++ /dev/null @@ -1,25 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -@@ -215,6 +_,12 @@ - LevelAccessor access = this.getWorldHandle(); - CraftBlock block = this.getBlock(); - -+ // Folia start - region threading -+ if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); -+ } -+ // Folia end - region threading -+ - if (block.getType() != this.getType()) { - if (!force) { - return false; -@@ -350,6 +_,9 @@ - - @Override - public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { -+ // Folia start - region threading -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); -+ // Folia end - region threading - this.requirePlaced(); - net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item); - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java.patch deleted file mode 100644 index 3878f0d..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -@@ -249,8 +_,8 @@ - net.minecraft.world.level.block.state.BlockState blockData = craftBlock.getNMS(); - BlockEntity tileEntity = craftBlock.getHandle().getBlockEntity(blockPosition); - // Paper start - block state snapshots -- boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT; -- CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot; -+ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT.get().booleanValue(); // Folia - region threading -+ CraftBlockEntityState.DISABLE_SNAPSHOT.set(Boolean.valueOf(!useSnapshot)); // Folia - region threading - try { - // Paper end - CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockPosition, blockData, tileEntity); -@@ -258,7 +_,7 @@ - return blockState; - // Paper start - } finally { -- CraftBlockEntityState.DISABLE_SNAPSHOT = prev; -+ CraftBlockEntityState.DISABLE_SNAPSHOT.set(Boolean.valueOf(prev)); // Folia - region threading - } - // Paper end - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java.patch deleted file mode 100644 index 672875e..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -@@ -50,7 +_,7 @@ - return syncEvent.callEvent() ? syncEvent.getCompletions() : com.google.common.collect.ImmutableList.of(); - } - }; -- server.getServer().processQueue.add(syncCompletions); -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(syncCompletions); // Folia - region threading - try { - final List legacyCompletions = syncCompletions.get(); - completions.removeIf(it -> !legacyCompletions.contains(it.suggestion())); // remove any suggestions that were removed -@@ -98,7 +_,7 @@ - return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); - } - }; -- server.getServer().processQueue.add(waitable); // Paper - Remove "this." -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(waitable); // Folia - region threading - try { - List offers = waitable.get(); - if (offers == null) { diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java.patch deleted file mode 100644 index 716014d..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java.patch +++ /dev/null @@ -1,31 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java -@@ -1,5 +_,6 @@ - package org.bukkit.craftbukkit.entity; - -+import net.minecraft.world.entity.Entity; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Projectile; - -@@ -38,6 +_,13 @@ - this.getHandle().hasBeenShot = beenShot; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.Projectile getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.Projectile)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public boolean canHitEntity(org.bukkit.entity.Entity entity) { - return this.getHandle().canHitEntityPublic(((CraftEntity) entity).getHandle()); -@@ -55,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.projectile.Projectile getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.Projectile) entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java.patch deleted file mode 100644 index 457f902..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java -@@ -142,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.projectile.AbstractArrow getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.AbstractArrow) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java.patch deleted file mode 100644 index 52c414a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -@@ -17,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.horse.AbstractHorse getHandleRaw() { -+ return (net.minecraft.world.entity.animal.horse.AbstractHorse)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.horse.AbstractHorse getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.horse.AbstractHorse) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java.patch deleted file mode 100644 index cd87cac..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java -@@ -15,8 +_,17 @@ - throw new UnsupportedOperationException("Not supported."); - } - // Paper start -+ -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.AbstractSkeleton getHandleRaw() { -+ return (net.minecraft.world.entity.monster.AbstractSkeleton)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.AbstractSkeleton getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.AbstractSkeleton) super.getHandle(); - } - // Paper end diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java.patch deleted file mode 100644 index a832c3d..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java -@@ -15,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.npc.AbstractVillager getHandleRaw() { -+ return (net.minecraft.world.entity.npc.AbstractVillager)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.npc.AbstractVillager getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Villager) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java.patch deleted file mode 100644 index 4422297..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractWindCharge.java -@@ -17,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.projectile.windcharge.AbstractWindCharge getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.windcharge.AbstractWindCharge) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java.patch deleted file mode 100644 index 68b3d72..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAgeable.java -@@ -63,8 +_,16 @@ - } - } - -+ // Folia start - region threading -+ @Override -+ public AgeableMob getHandleRaw() { -+ return (AgeableMob)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AgeableMob getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AgeableMob) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java.patch deleted file mode 100644 index 2130f95..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAllay.java -@@ -16,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public Allay getHandleRaw() { -+ return (Allay)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Allay getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Allay) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java.patch deleted file mode 100644 index 534b271..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAmbient.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public AmbientCreature getHandleRaw() { -+ return (AmbientCreature)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AmbientCreature getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AmbientCreature) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java.patch deleted file mode 100644 index cb59aca..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAnimals.java -@@ -15,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public Animal getHandleRaw() { -+ return (Animal)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Animal getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Animal) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java.patch deleted file mode 100644 index 9a6c783..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java -@@ -28,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.AreaEffectCloud getHandleRaw() { -+ return (net.minecraft.world.entity.AreaEffectCloud)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.AreaEffectCloud getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.AreaEffectCloud) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java.patch deleted file mode 100644 index 23a0f4a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmadillo.java -@@ -11,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.animal.armadillo.Armadillo getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.armadillo.Armadillo) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java.patch deleted file mode 100644 index fee2c1d..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArmorStand.java -@@ -20,8 +_,16 @@ - return "CraftArmorStand"; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.decoration.ArmorStand getHandleRaw() { -+ return (net.minecraft.world.entity.decoration.ArmorStand)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.decoration.ArmorStand getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.decoration.ArmorStand) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java.patch deleted file mode 100644 index b2861d3..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -@@ -26,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.projectile.Arrow getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.Arrow) this.entity; - } - -@@ -89,6 +_,13 @@ - this.getHandle().setPotionContents(new PotionContents(old.potion(), old.customColor(), old.customEffects().stream().filter((mobEffect) -> !mobEffect.getEffect().equals(minecraft)).toList(), old.customName())); - return true; - } -+ -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.AbstractArrow getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.AbstractArrow)this.entity; -+ } -+ // Folia end - region threading - - @Override - public void setBasePotionData(PotionData data) { diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java.patch deleted file mode 100644 index 7663d8c..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.axolotl.Axolotl getHandleRaw() { -+ return (net.minecraft.world.entity.animal.axolotl.Axolotl)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.axolotl.Axolotl getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.axolotl.Axolotl) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java.patch deleted file mode 100644 index 79c2c7a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -@@ -8,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.ambient.Bat getHandleRaw() { -+ return (net.minecraft.world.entity.ambient.Bat)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.ambient.Bat getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.ambient.Bat) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java.patch deleted file mode 100644 index 81913e7..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -@@ -13,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Bee getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Bee)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Bee getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Bee) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java.patch deleted file mode 100644 index c61b760..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlaze.java -@@ -8,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Blaze getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Blaze)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Blaze getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Blaze) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java.patch deleted file mode 100644 index 4dde751..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockAttachedEntity.java -@@ -8,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public BlockAttachedEntity getHandleRaw() { -+ return (BlockAttachedEntity)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public BlockAttachedEntity getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (BlockAttachedEntity) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java.patch deleted file mode 100644 index 6a00782..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBlockDisplay.java -@@ -12,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.Display.BlockDisplay getHandleRaw() { -+ return (net.minecraft.world.entity.Display.BlockDisplay)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.Display.BlockDisplay getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.Display.BlockDisplay) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java.patch deleted file mode 100644 index ab24dda..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -@@ -101,8 +_,16 @@ - return CraftBoat.boatStatusFromNms(this.getHandle().status); - } - -+ // Folia start - region threading -+ @Override -+ public AbstractBoat getHandleRaw() { -+ return (AbstractBoat)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractBoat getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractBoat) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java.patch deleted file mode 100644 index 1e2b9b0..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBogged.java -@@ -12,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.monster.Bogged getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Bogged) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java.patch deleted file mode 100644 index 4c1f9ec..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBreeze.java -@@ -13,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.monster.breeze.Breeze getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.breeze.Breeze) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java.patch deleted file mode 100644 index aaeedf3..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBreezeWindCharge.java -@@ -10,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.projectile.windcharge.BreezeWindCharge getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.windcharge.BreezeWindCharge) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java.patch deleted file mode 100644 index 2d93f76..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCamel.java -@@ -11,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.camel.Camel getHandleRaw() { -+ return (net.minecraft.world.entity.animal.camel.Camel)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.camel.Camel getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.camel.Camel) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java.patch deleted file mode 100644 index 5531c2d..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -@@ -18,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Cat getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Cat)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Cat getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Cat) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java.patch deleted file mode 100644 index f17a382..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCaveSpider.java -@@ -8,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.CaveSpider getHandleRaw() { -+ return (net.minecraft.world.entity.monster.CaveSpider)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.CaveSpider getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.CaveSpider) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java.patch deleted file mode 100644 index f581c90..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java -@@ -15,8 +_,16 @@ - this.inventory = new CraftInventory(entity); - } - -+ // Folia start - region threading -+ @Override -+ public AbstractChestBoat getHandleRaw() { -+ return (AbstractChestBoat)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractChestBoat getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractChestBoat) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java.patch deleted file mode 100644 index c90d991..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestedHorse.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public AbstractChestedHorse getHandleRaw() { -+ return (AbstractChestedHorse)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractChestedHorse getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractChestedHorse) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java.patch deleted file mode 100644 index 175e8b0..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Chicken getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Chicken)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Chicken getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Chicken) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java.patch deleted file mode 100644 index 416849a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Cod getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Cod)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Cod getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Cod) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java.patch deleted file mode 100644 index 4504b63..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftComplexPart.java -@@ -32,8 +_,16 @@ - return this.getParent().isValid(); - } - -+ // Folia start - region threading -+ @Override -+ public EnderDragonPart getHandleRaw() { -+ return (EnderDragonPart)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public EnderDragonPart getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (EnderDragonPart) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java.patch deleted file mode 100644 index e87a4b2..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Cow getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Cow)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Cow getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Cow) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java.patch deleted file mode 100644 index 66386c6..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreaking.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public Creaking getHandleRaw() { -+ return (Creaking)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Creaking getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Creaking) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java.patch deleted file mode 100644 index 1769463..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreature.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public PathfinderMob getHandleRaw() { -+ return (PathfinderMob)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public PathfinderMob getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (PathfinderMob) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java.patch deleted file mode 100644 index eaa7b37..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCreeper.java -@@ -87,6 +_,13 @@ - this.getHandle().ignite(); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Creeper getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Creeper)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Entity getIgniter() { - return (this.getHandle().entityIgniter != null) ? this.getHandle().entityIgniter.getBukkitEntity() : null; -@@ -94,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.monster.Creeper getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Creeper) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java.patch deleted file mode 100644 index 7c0501d..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDisplay.java -@@ -12,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.Display getHandleRaw() { -+ return (net.minecraft.world.entity.Display)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.Display getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.Display) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java.patch deleted file mode 100644 index 6633c80..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Dolphin getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Dolphin)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Dolphin getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Dolphin) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java.patch deleted file mode 100644 index 5187a46..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDrowned.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Drowned getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Drowned)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Drowned getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Drowned) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java.patch deleted file mode 100644 index 7eebe8d..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEgg.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public ThrownEgg getHandleRaw() { -+ return (ThrownEgg)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public ThrownEgg getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (ThrownEgg) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java.patch deleted file mode 100644 index 5305f4a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderCrystal.java -@@ -39,8 +_,16 @@ - } - } - -+ // Folia start - region threading -+ @Override -+ public EndCrystal getHandleRaw() { -+ return (EndCrystal)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public EndCrystal getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (EndCrystal) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java.patch deleted file mode 100644 index 10e70ad..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -@@ -30,8 +_,16 @@ - return builder.build(); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.boss.enderdragon.EnderDragon getHandleRaw() { -+ return (net.minecraft.world.entity.boss.enderdragon.EnderDragon)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.boss.enderdragon.EnderDragon getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.boss.enderdragon.EnderDragon) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java.patch deleted file mode 100644 index 3812c41..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java -@@ -16,8 +_,16 @@ - return (EnderDragon) super.getParent(); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.boss.EnderDragonPart getHandleRaw() { -+ return (net.minecraft.world.entity.boss.EnderDragonPart)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.boss.EnderDragonPart getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.boss.EnderDragonPart) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java.patch deleted file mode 100644 index 0b3de28..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderPearl.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public ThrownEnderpearl getHandleRaw() { -+ return (ThrownEnderpearl)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public ThrownEnderpearl getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (ThrownEnderpearl) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java.patch deleted file mode 100644 index 2c9e06e..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java -@@ -15,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public EyeOfEnder getHandleRaw() { -+ return (EyeOfEnder)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public EyeOfEnder getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (EyeOfEnder) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java.patch deleted file mode 100644 index 664605b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -@@ -62,8 +_,16 @@ - } - // Paper end - -+ // Folia start - region threading -+ @Override -+ public EnderMan getHandleRaw() { -+ return (EnderMan)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public EnderMan getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (EnderMan) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java.patch deleted file mode 100644 index 0873cc7..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Endermite getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Endermite)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Endermite getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Endermite) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java.patch deleted file mode 100644 index 0c29a4c..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java.patch +++ /dev/null @@ -1,134 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -83,6 +_,11 @@ - return this.apiScheduler; - }; - // Paper end - Folia schedulers -+ // Folia start - region threading -+ public boolean isPurged() { -+ return this.taskScheduler.isRetired(); -+ } -+ // Folia end - region threading - - public CraftEntity(final CraftServer server, final Entity entity) { - this.server = server; -@@ -240,6 +_,11 @@ - - @Override - public boolean teleport(Location location, TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { -+ // Folia start - region threading -+ if (true) { -+ throw new UnsupportedOperationException("Must use teleportAsync while in region threading"); -+ } -+ // Folia end - region threading - // Paper end - Preconditions.checkArgument(location != null, "location cannot be null"); - location.checkFinite(); -@@ -529,6 +_,7 @@ - } - - public Entity getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return this.entity; - } - -@@ -722,7 +_,7 @@ - ImmutableSet.Builder players = ImmutableSet.builder(); - - ServerLevel world = ((CraftWorld) this.getWorld()).getHandle(); -- ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId()); -+ ChunkMap.TrackedEntity entityTracker = this.getHandle().moonrise$getTrackedEntity(); // Folia - region threading - - if (entityTracker != null) { - for (ServerPlayerConnection connection : entityTracker.seenBy) { -@@ -1026,7 +_,7 @@ - } - - ServerLevel world = ((CraftWorld) this.getWorld()).getHandle(); -- ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId()); -+ ChunkMap.TrackedEntity entityTracker = this.getHandle().moonrise$getTrackedEntity(); // Folia - region threading - - if (entityTracker == null) { - return; -@@ -1045,7 +_,7 @@ - } - - ServerLevel world = ((CraftWorld) this.getWorld()).getHandle(); -- ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId()); -+ ChunkMap.TrackedEntity entityTracker = this.entity.moonrise$getTrackedEntity(); // Folia - region threading - - if (entityTracker == null) { - return; -@@ -1079,29 +_,43 @@ - location.checkFinite(); - Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call. - -- net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle(); -+ // Folia start - region threading - java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); -+ java.util.function.Consumer run = (Entity nmsEntity) -> { -+ boolean success = nmsEntity.teleportAsync( -+ ((CraftWorld)locationClone.getWorld()).getHandle(), -+ new net.minecraft.world.phys.Vec3(locationClone.getX(), locationClone.getY(), locationClone.getZ()), -+ locationClone.getYaw(), locationClone.getPitch(), net.minecraft.world.phys.Vec3.ZERO, -+ cause == null ? TeleportCause.UNKNOWN : cause, -+ Entity.TELEPORT_FLAG_LOAD_CHUNK | Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS, // preserve behavior with old API: dismount the entity so it can teleport -+ (Entity entityTp) -> { -+ ret.complete(Boolean.TRUE); -+ } -+ ); -+ if (!success) { -+ ret.complete(Boolean.FALSE); -+ } -+ }; -+ if (org.bukkit.Bukkit.isOwnedByCurrentRegion(this)) { -+ run.accept(this.getHandle()); -+ return ret; -+ } -+ boolean scheduled = this.taskScheduler.schedule( -+ // success -+ run, -+ // retired -+ (Entity nmsEntity) -> { -+ ret.complete(Boolean.FALSE); -+ }, -+ 1L -+ ); - -- world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), -- this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL, (list) -> { -- net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { -- final net.minecraft.server.level.ServerChunkCache chunkCache = world.getChunkSource(); -- for (final net.minecraft.world.level.chunk.ChunkAccess chunk : list) { -- chunkCache.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); -- } -- try { -- ret.complete(CraftEntity.this.teleport(locationClone, cause, teleportFlags) ? Boolean.TRUE : Boolean.FALSE); -- } catch (Throwable throwable) { -- if (throwable instanceof ThreadDeath) { -- throw (ThreadDeath)throwable; -- } -- net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable); -- ret.completeExceptionally(throwable); -- } -- }); -- }); -+ if (!scheduled) { -+ ret.complete(Boolean.FALSE); -+ } - - return ret; -+ // Folia end - region threading - } - // Paper end - more teleport API / async chunk API - -@@ -1214,8 +_,7 @@ - // Paper start - tracked players API - @Override - public Set getTrackedPlayers() { -- ServerLevel world = (net.minecraft.server.level.ServerLevel)this.entity.level(); -- ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.entity.getId()); -+ ChunkMap.TrackedEntity tracker = this.entity.moonrise$getTrackedEntity(); // Folia - region threading - if (tracker == null) { - return java.util.Collections.emptySet(); - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java.patch deleted file mode 100644 index a3ca20a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java -@@ -11,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Evoker getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Evoker)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Evoker getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Evoker) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java.patch deleted file mode 100644 index b95509f..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvokerFangs.java -@@ -11,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.EvokerFangs getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.EvokerFangs)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.EvokerFangs getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.EvokerFangs) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java.patch deleted file mode 100644 index d74268b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftExperienceOrb.java -@@ -42,8 +_,16 @@ - } - // Paper end - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.ExperienceOrb getHandleRaw() { -+ return (net.minecraft.world.entity.ExperienceOrb)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.ExperienceOrb getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.ExperienceOrb) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java.patch deleted file mode 100644 index 29f0fef..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -@@ -14,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public FallingBlockEntity getHandleRaw() { -+ return (FallingBlockEntity)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public FallingBlockEntity getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (FallingBlockEntity) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java.patch deleted file mode 100644 index d9d7ebe..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java -@@ -83,8 +_,16 @@ - } - // Paper end - Expose power on fireball projectiles - -+ // Folia start - region threading -+ @Override -+ public AbstractHurtingProjectile getHandleRaw() { -+ return (AbstractHurtingProjectile)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractHurtingProjectile getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractHurtingProjectile) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java.patch deleted file mode 100644 index ab889ea..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -@@ -37,8 +_,16 @@ - // Paper end - Expose firework item directly - } - -+ // Folia start - region threading -+ @Override -+ public FireworkRocketEntity getHandleRaw() { -+ return (FireworkRocketEntity)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public FireworkRocketEntity getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (FireworkRocketEntity) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java.patch deleted file mode 100644 index 6db5db9..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public AbstractFish getHandleRaw() { -+ return (AbstractFish)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractFish getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractFish) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java.patch deleted file mode 100644 index 91dcb18..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -@@ -14,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public FishingHook getHandleRaw() { -+ return (FishingHook)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public FishingHook getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (FishingHook) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java.patch deleted file mode 100644 index df6bed1..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFlying.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public FlyingMob getHandleRaw() { -+ return (FlyingMob)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public FlyingMob getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (FlyingMob) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java.patch deleted file mode 100644 index 0638e55..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -@@ -14,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Fox getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Fox)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Fox getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Fox) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java.patch deleted file mode 100644 index 06dcb78..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java -@@ -18,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public Frog getHandleRaw() { -+ return (Frog)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Frog getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Frog) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java.patch deleted file mode 100644 index 549a042..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Ghast getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Ghast)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Ghast getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Ghast) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java.patch deleted file mode 100644 index b55d541..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGiant.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Giant getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Giant)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Giant getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Giant) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java.patch deleted file mode 100644 index a72eb37..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowItemFrame.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.decoration.GlowItemFrame getHandleRaw() { -+ return (net.minecraft.world.entity.decoration.GlowItemFrame)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.decoration.GlowItemFrame getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.decoration.GlowItemFrame) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java.patch deleted file mode 100644 index 5512462..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGlowSquid.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.GlowSquid getHandleRaw() { -+ return (net.minecraft.world.entity.GlowSquid)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.GlowSquid getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.GlowSquid) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java.patch deleted file mode 100644 index c57a479..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.goat.Goat getHandleRaw() { -+ return (net.minecraft.world.entity.animal.goat.Goat)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.goat.Goat getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.goat.Goat) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java.patch deleted file mode 100644 index 64cf726..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGolem.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public AbstractGolem getHandleRaw() { -+ return (AbstractGolem)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractGolem getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractGolem) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java.patch deleted file mode 100644 index dab0db8..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGuardian.java -@@ -13,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Guardian getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Guardian)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Guardian getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Guardian) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java.patch deleted file mode 100644 index 2fcfe42..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHanging.java -@@ -57,8 +_,16 @@ - return CraftBlock.notchToBlockFace(direction); - } - -+ // Folia start - region threading -+ @Override -+ public HangingEntity getHandleRaw() { -+ return (HangingEntity)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public HangingEntity getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (HangingEntity) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java.patch deleted file mode 100644 index bca67fb..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHoglin.java -@@ -51,8 +_,16 @@ - return this.getHandle().isConverting(); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.hoglin.Hoglin getHandleRaw() { -+ return (net.minecraft.world.entity.monster.hoglin.Hoglin)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.hoglin.Hoglin getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.hoglin.Hoglin) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java.patch deleted file mode 100644 index b9122e8..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHorse.java -@@ -13,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.horse.Horse getHandleRaw() { -+ return (net.minecraft.world.entity.animal.horse.Horse)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.horse.Horse getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.horse.Horse) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java.patch deleted file mode 100644 index 1aa112a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -306,8 +_,16 @@ - this.mode = mode; - } - -+ // Folia start - region threading -+ @Override -+ public Player getHandleRaw() { -+ return (Player)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Player getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Player) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java.patch deleted file mode 100644 index a279527..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllager.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public AbstractIllager getHandleRaw() { -+ return (AbstractIllager)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractIllager getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractIllager) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java.patch deleted file mode 100644 index da84a0e..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIllusioner.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Illusioner getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Illusioner)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Illusioner getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Illusioner) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java.patch deleted file mode 100644 index 44334f9..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftInteraction.java -@@ -12,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.Interaction getHandleRaw() { -+ return (net.minecraft.world.entity.Interaction)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.Interaction getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.Interaction) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java.patch deleted file mode 100644 index 1ea99d1..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -@@ -8,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.IronGolem getHandleRaw() { -+ return (net.minecraft.world.entity.animal.IronGolem)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.IronGolem getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.IronGolem) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java.patch deleted file mode 100644 index 724cb38..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -@@ -18,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public ItemEntity getHandleRaw() { -+ return (ItemEntity)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public ItemEntity getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (ItemEntity) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java.patch deleted file mode 100644 index 4aacec6..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemDisplay.java -@@ -13,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.Display.ItemDisplay getHandleRaw() { -+ return (net.minecraft.world.entity.Display.ItemDisplay)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.Display.ItemDisplay getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.Display.ItemDisplay) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java.patch deleted file mode 100644 index b965c8e..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java -@@ -157,8 +_,16 @@ - this.getHandle().fixed = fixed; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.decoration.ItemFrame getHandleRaw() { -+ return (net.minecraft.world.entity.decoration.ItemFrame)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.decoration.ItemFrame getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.decoration.ItemFrame) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java.patch deleted file mode 100644 index 537c2d8..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLargeFireball.java -@@ -14,8 +_,16 @@ - this.getHandle().explosionPower = (int) yield; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.LargeFireball getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.LargeFireball)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.LargeFireball getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.LargeFireball) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java.patch deleted file mode 100644 index ea0d25c..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLeash.java -@@ -24,6 +_,13 @@ - return BlockFace.SELF; - } - -+ // Folia start - region threading -+ @Override -+ public LeashFenceKnotEntity getHandleRaw() { -+ return (LeashFenceKnotEntity)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public BlockFace getAttachedFace() { - // Leash hitch has no facing direction, so we return self -@@ -37,6 +_,7 @@ - - @Override - public LeashFenceKnotEntity getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (LeashFenceKnotEntity) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java.patch deleted file mode 100644 index 5abd367..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -@@ -41,8 +_,16 @@ - this.getHandle().setCause((player != null) ? ((CraftPlayer) player).getHandle() : null); - } - -+ // Folia start - region threading -+ @Override -+ public LightningBolt getHandleRaw() { -+ return (LightningBolt)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public LightningBolt getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (LightningBolt) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java.patch deleted file mode 100644 index 5eff551..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -487,6 +_,13 @@ - this.getHandle().invulnerableTime = ticks; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.LivingEntity getHandleRaw() { -+ return (net.minecraft.world.entity.LivingEntity)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public int getNoActionTicks() { - return this.getHandle().getNoActionTime(); -@@ -500,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.LivingEntity getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.LivingEntity) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java.patch deleted file mode 100644 index 36737c6..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -@@ -14,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.horse.Llama getHandleRaw() { -+ return (net.minecraft.world.entity.animal.horse.Llama)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.horse.Llama getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.horse.Llama) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java.patch deleted file mode 100644 index ea0e15a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.LlamaSpit getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.LlamaSpit)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.LlamaSpit getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.LlamaSpit) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java.patch deleted file mode 100644 index c51a116..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMagmaCube.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.MagmaCube getHandleRaw() { -+ return (net.minecraft.world.entity.monster.MagmaCube)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.MagmaCube getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.MagmaCube) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java.patch deleted file mode 100644 index b142aea..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMarker.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.Marker getHandleRaw() { -+ return (net.minecraft.world.entity.Marker)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.Marker getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.Marker) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java.patch deleted file mode 100644 index a576a85..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java -@@ -77,8 +_,16 @@ - } - // Paper end - -+ // Folia start - region threading -+ @Override -+ public AbstractMinecart getHandleRaw() { -+ return (AbstractMinecart)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractMinecart getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractMinecart) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java.patch deleted file mode 100644 index 393aef3..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java -@@ -20,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public MinecartCommandBlock getHandleRaw() { -+ return (MinecartCommandBlock)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public MinecartCommandBlock getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (MinecartCommandBlock) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java.patch deleted file mode 100644 index a17b22e..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java -@@ -13,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public AbstractMinecartContainer getHandleRaw() { -+ return (AbstractMinecartContainer)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractMinecartContainer getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractMinecartContainer) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java.patch deleted file mode 100644 index 2163179..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java -@@ -11,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public MinecartFurnace getHandleRaw() { -+ return (MinecartFurnace)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public MinecartFurnace getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (MinecartFurnace) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java.patch deleted file mode 100644 index 842f001..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java -@@ -34,8 +_,17 @@ - ((MinecartHopper) this.getHandle()).setEnabled(enabled); - } - // Paper start -+ -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.vehicle.MinecartHopper getHandleRaw() { -+ return (net.minecraft.world.entity.vehicle.MinecartHopper)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.vehicle.MinecartHopper getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.vehicle.MinecartHopper) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java.patch deleted file mode 100644 index d6e85bf..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java -@@ -162,8 +_,16 @@ - this.getHandle().getSpawner().spawnRange = spawnRange; - } - -+ // Folia start - region threading -+ @Override -+ public MinecartSpawner getHandleRaw() { -+ return (MinecartSpawner)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public MinecartSpawner getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (MinecartSpawner) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java.patch deleted file mode 100644 index 5eb048e..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartTNT.java -@@ -72,8 +_,16 @@ - this.getHandle().explode(power); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.vehicle.MinecartTNT getHandleRaw() { -+ return (net.minecraft.world.entity.vehicle.MinecartTNT)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public MinecartTNT getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (MinecartTNT) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java.patch deleted file mode 100644 index e0f12ee..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java.patch +++ /dev/null @@ -1,28 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -54,8 +_,16 @@ - return (sound != null) ? CraftSound.minecraftToBukkit(sound) : null; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.Mob getHandleRaw() { -+ return (net.minecraft.world.entity.Mob)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.Mob getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.Mob) this.entity; - } - -@@ -63,7 +_,7 @@ - @Override - public void setHandle(net.minecraft.world.entity.Entity entity) { - super.setHandle(entity); -- paperPathfinder.setHandle(getHandle()); -+ paperPathfinder.setHandle((net.minecraft.world.entity.Mob)entity); // Folia - region threading - } - // Paper end - Mob Pathfinding API - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java.patch deleted file mode 100644 index cae2bf6..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMonster.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Monster getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Monster)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Monster getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Monster) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java.patch deleted file mode 100644 index 6e24796..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMushroomCow.java -@@ -19,6 +_,13 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.MushroomCow getHandleRaw() { -+ return (net.minecraft.world.entity.animal.MushroomCow)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public boolean hasEffectsForNextStew() { - SuspiciousStewEffects stewEffects = this.getHandle().stewEffects; -@@ -94,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.animal.MushroomCow getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.MushroomCow) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java.patch deleted file mode 100644 index 75bb112..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftOcelot.java -@@ -9,8 +_,16 @@ - super(server, ocelot); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Ocelot getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Ocelot)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Ocelot getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Ocelot) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java.patch deleted file mode 100644 index 64680e5..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftOminousItemSpawner.java -@@ -13,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.OminousItemSpawner getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.OminousItemSpawner) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java.patch deleted file mode 100644 index 8fb7a09..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java -@@ -50,8 +_,16 @@ - return false; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.decoration.Painting getHandleRaw() { -+ return (net.minecraft.world.entity.decoration.Painting)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.decoration.Painting getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.decoration.Painting) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java.patch deleted file mode 100644 index 11b5bcc..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -@@ -11,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Panda getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Panda)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Panda getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Panda) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java.patch deleted file mode 100644 index d013a3b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftParrot.java -@@ -11,8 +_,16 @@ - super(server, parrot); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Parrot getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Parrot)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Parrot getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Parrot) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java.patch deleted file mode 100644 index 68258f7..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Phantom getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Phantom)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Phantom getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Phantom) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java.patch deleted file mode 100644 index 25fa7ac..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java -@@ -55,8 +_,16 @@ - return Material.CARROT_ON_A_STICK; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Pig getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Pig)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Pig getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Pig) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java.patch deleted file mode 100644 index 1cb73fc..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPigZombie.java -@@ -30,8 +_,16 @@ - return this.getAnger() > 0; - } - -+ // Folia start - region threading -+ @Override -+ public ZombifiedPiglin getHandleRaw() { -+ return (ZombifiedPiglin)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public ZombifiedPiglin getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (ZombifiedPiglin) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java.patch deleted file mode 100644 index f012272..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -@@ -75,8 +_,16 @@ - return new CraftInventory(this.getHandle().inventory); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.piglin.Piglin getHandleRaw() { -+ return (net.minecraft.world.entity.monster.piglin.Piglin)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.piglin.Piglin getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.piglin.Piglin) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java.patch deleted file mode 100644 index 72de3cb..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinAbstract.java -@@ -95,8 +_,16 @@ - public void setBreed(boolean b) { - } - -+ // Folia start - region threading -+ @Override -+ public AbstractPiglin getHandleRaw() { -+ return (AbstractPiglin)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public AbstractPiglin getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (AbstractPiglin) super.getHandle(); - } - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java.patch deleted file mode 100644 index f6f60bc..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglinBrute.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.piglin.PiglinBrute getHandleRaw() { -+ return (net.minecraft.world.entity.monster.piglin.PiglinBrute)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.piglin.PiglinBrute getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.piglin.PiglinBrute) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java.patch deleted file mode 100644 index 8df4340..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPillager.java -@@ -11,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Pillager getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Pillager)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Pillager getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Pillager) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch deleted file mode 100644 index 548f6fb..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch +++ /dev/null @@ -1,77 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -673,7 +_,7 @@ - - @Override - public void kickPlayer(String message) { -- org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot -+ //org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot // Folia - thread-safe now, as it will simply delay the kick - this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true), org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause - } - -@@ -691,7 +_,7 @@ - - @Override - public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { -- org.spigotmc.AsyncCatcher.catchOp("player kick"); -+ //org.spigotmc.AsyncCatcher.catchOp("player kick"); // Folia - region threading - no longer needed - final ServerGamePacketListenerImpl connection = this.getHandle().connection; - if (connection != null) { - connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); -@@ -1411,6 +_,11 @@ - - @Override - public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { -+ // Folia start - region threading -+ if (true) { -+ throw new UnsupportedOperationException("Must use teleportAsync while in region threading"); -+ } -+ // Folia end - region threading - Set relativeArguments; - Set allFlags; - if (flags.length == 0) { -@@ -2075,7 +_,7 @@ - private void unregisterEntity(Entity other) { - // Paper end - ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; -- ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); -+ ChunkMap.TrackedEntity entry = other.moonrise$getTrackedEntity(); // Folia - region threading - if (entry != null) { - entry.removePlayer(this.getHandle()); - } -@@ -2172,7 +_,7 @@ - if (original != null) otherPlayer.setUUID(original); // Paper - uuid override - } - -- ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); -+ ChunkMap.TrackedEntity entry = other.moonrise$getTrackedEntity(); // Folia - region threading - if (entry != null && !entry.seenBy.contains(this.getHandle().connection)) { - entry.updatePlayer(this.getHandle()); - } -@@ -2321,9 +_,16 @@ - return this; - } - -+ // Folia start - region threading -+ @Override -+ public ServerPlayer getHandleRaw() { -+ return (ServerPlayer)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public ServerPlayer getHandle() { -- return (ServerPlayer) this.entity; -+ return (ServerPlayer) this.entity; // Folia - region threading - no checks for players, as it's a total mess - } - - public void setHandle(final ServerPlayer entity) { -@@ -3355,7 +_,7 @@ - { - if ( CraftPlayer.this.getHealth() <= 0 && CraftPlayer.this.isOnline() ) - { -- CraftPlayer.this.server.getServer().getPlayerList().respawn( CraftPlayer.this.getHandle(), false, Entity.RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.PLUGIN ); -+ CraftPlayer.this.getHandle().respawn(null, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.PLUGIN); // Folia - region threading - } - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java.patch deleted file mode 100644 index fd4256b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -@@ -8,8 +_,17 @@ - public CraftPolarBear(CraftServer server, net.minecraft.world.entity.animal.PolarBear entity) { - super(server, entity); - } -+ -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.PolarBear getHandleRaw() { -+ return (net.minecraft.world.entity.animal.PolarBear)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.PolarBear getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.PolarBear) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java.patch deleted file mode 100644 index 1aa0709..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java -@@ -12,8 +_,16 @@ - - // Paper - moved to AbstractProjectile - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.Projectile getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.Projectile)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.Projectile getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.Projectile) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java.patch deleted file mode 100644 index c1c8c99..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPufferFish.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public Pufferfish getHandleRaw() { -+ return (Pufferfish)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Pufferfish getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Pufferfish) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java.patch deleted file mode 100644 index 932d02f..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Rabbit getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Rabbit)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Rabbit getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Rabbit) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java.patch deleted file mode 100644 index 438d095..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java -@@ -16,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.raid.Raider getHandleRaw() { -+ return (net.minecraft.world.entity.raid.Raider)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.raid.Raider getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.raid.Raider) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java.patch deleted file mode 100644 index f7a51b1..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Ravager getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Ravager)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Ravager getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Ravager) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java.patch deleted file mode 100644 index b9ee51c..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Salmon getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Salmon)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Salmon getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Salmon) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java.patch deleted file mode 100644 index 0eacda5..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSheep.java -@@ -29,8 +_,16 @@ - this.getHandle().setSheared(flag); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Sheep getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Sheep)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Sheep getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Sheep) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java.patch deleted file mode 100644 index fb0fd06..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulker.java -@@ -18,8 +_,16 @@ - return "CraftShulker"; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Shulker getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Shulker)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Shulker getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Shulker) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java.patch deleted file mode 100644 index a2975c6..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java -@@ -69,8 +_,16 @@ - return "CraftShulkerBullet"; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.ShulkerBullet getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.ShulkerBullet)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.ShulkerBullet getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.ShulkerBullet) this.entity; - } - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java.patch deleted file mode 100644 index e8a508c..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSilverfish.java -@@ -8,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Silverfish getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Silverfish)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Silverfish getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Silverfish) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java.patch deleted file mode 100644 index 93b569c..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSizedFireball.java -@@ -27,8 +_,16 @@ - this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); - } - -+ // Folia start - region threading -+ @Override -+ public Fireball getHandleRaw() { -+ return (Fireball)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Fireball getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Fireball) this.entity; - } - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java.patch deleted file mode 100644 index 7405f2a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java -@@ -31,8 +_,16 @@ - } - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Skeleton getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Skeleton)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Skeleton getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Skeleton) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java.patch deleted file mode 100644 index 7f4e927..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeletonHorse.java -@@ -20,8 +_,16 @@ - return Variant.SKELETON_HORSE; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.horse.SkeletonHorse getHandleRaw() { -+ return (net.minecraft.world.entity.animal.horse.SkeletonHorse)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.horse.SkeletonHorse getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.horse.SkeletonHorse) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java.patch deleted file mode 100644 index 2865ab4..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java -@@ -19,8 +_,16 @@ - this.getHandle().setSize(size, /* true */ getHandle().isAlive()); // Paper - fix dead slime setSize invincibility - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Slime getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Slime)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Slime getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Slime) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java.patch deleted file mode 100644 index f823c87..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSmallFireball.java -@@ -8,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.SmallFireball getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.SmallFireball)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.SmallFireball getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.SmallFireball) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java.patch deleted file mode 100644 index dae2ba5..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java -@@ -16,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.sniffer.Sniffer getHandleRaw() { -+ return (net.minecraft.world.entity.animal.sniffer.Sniffer)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.sniffer.Sniffer getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.sniffer.Sniffer) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java.patch deleted file mode 100644 index 1d9f493..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowball.java -@@ -8,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.Snowball getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.Snowball)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.Snowball getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.Snowball) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java.patch deleted file mode 100644 index e77bebd..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -@@ -19,8 +_,16 @@ - this.getHandle().setPumpkin(!derpMode); - } - -+ // Folia start - region threading -+ @Override -+ public SnowGolem getHandleRaw() { -+ return (SnowGolem)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public SnowGolem getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (SnowGolem) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java.patch deleted file mode 100644 index 2cdd926..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpectralArrow.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.SpectralArrow getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.SpectralArrow)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.SpectralArrow getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.SpectralArrow) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java.patch deleted file mode 100644 index bf5426a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpellcaster.java -@@ -12,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public SpellcasterIllager getHandleRaw() { -+ return (SpellcasterIllager)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public SpellcasterIllager getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (SpellcasterIllager) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java.patch deleted file mode 100644 index 40a1f9e..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSpider.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Spider getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Spider)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Spider getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Spider) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java.patch deleted file mode 100644 index e6186e3..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSquid.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Squid getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Squid)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Squid getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Squid) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java.patch deleted file mode 100644 index 804621f..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftStrider.java -@@ -65,8 +_,16 @@ - return Material.WARPED_FUNGUS_ON_A_STICK; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Strider getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Strider)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Strider getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Strider) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java.patch deleted file mode 100644 index c9a241f..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java -@@ -42,8 +_,16 @@ - this.getHandle().setFuse(fuseTicks); - } - -+ // Folia start - region threading -+ @Override -+ public PrimedTnt getHandleRaw() { -+ return (PrimedTnt)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public PrimedTnt getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (PrimedTnt) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java.patch deleted file mode 100644 index 36f1ac2..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public Tadpole getHandleRaw() { -+ return (Tadpole)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Tadpole getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Tadpole) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java.patch deleted file mode 100644 index e194704..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTameableAnimal.java -@@ -12,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public TamableAnimal getHandleRaw() { -+ return (TamableAnimal)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public TamableAnimal getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (TamableAnimal) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java.patch deleted file mode 100644 index 6320217..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTextDisplay.java -@@ -13,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.Display.TextDisplay getHandleRaw() { -+ return (net.minecraft.world.entity.Display.TextDisplay)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.Display.TextDisplay getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.Display.TextDisplay) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java.patch deleted file mode 100644 index 8b7402b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrowableProjectile.java -@@ -26,8 +_,16 @@ - this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); - } - -+ // Folia start - region threading -+ @Override -+ public ThrowableItemProjectile getHandleRaw() { -+ return (ThrowableItemProjectile)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public ThrowableItemProjectile getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (ThrowableItemProjectile) this.entity; - } - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java.patch deleted file mode 100644 index e7462eb..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownExpBottle.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public ThrownExperienceBottle getHandleRaw() { -+ return (ThrownExperienceBottle)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public ThrownExperienceBottle getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (ThrownExperienceBottle) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java.patch deleted file mode 100644 index 5abe8ad..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -@@ -61,8 +_,17 @@ - this.getHandle().splash(null); - } - // Paper end -+ -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.ThrownPotion getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.ThrownPotion)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.ThrownPotion getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.ThrownPotion) this.entity; - } - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java.patch deleted file mode 100644 index cdd53c8..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTraderLlama.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.horse.TraderLlama getHandleRaw() { -+ return (net.minecraft.world.entity.animal.horse.TraderLlama)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.horse.TraderLlama getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.horse.TraderLlama) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java.patch deleted file mode 100644 index 6e20eff..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -@@ -12,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public ThrownTrident getHandleRaw() { -+ return (ThrownTrident)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public ThrownTrident getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (ThrownTrident) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java.patch deleted file mode 100644 index b2e108a..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java -@@ -13,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.TropicalFish getHandleRaw() { -+ return (net.minecraft.world.entity.animal.TropicalFish)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.TropicalFish getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.TropicalFish) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java.patch deleted file mode 100644 index c64ced8..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Turtle getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Turtle)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Turtle getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Turtle) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java.patch deleted file mode 100644 index 5d6d800..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -@@ -13,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Vex getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Vex)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Vex getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Vex) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java.patch deleted file mode 100644 index ff3632e..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -32,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.npc.Villager getHandleRaw() { -+ return (net.minecraft.world.entity.npc.Villager)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.npc.Villager getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.npc.Villager) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java.patch deleted file mode 100644 index 4c8aad3..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -@@ -14,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.ZombieVillager getHandleRaw() { -+ return (net.minecraft.world.entity.monster.ZombieVillager)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.ZombieVillager getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.ZombieVillager) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java.patch deleted file mode 100644 index e404089..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVindicator.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Vindicator getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Vindicator)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Vindicator getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Vindicator) super.getHandle(); - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java.patch deleted file mode 100644 index 0dcf330..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -@@ -9,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.npc.WanderingTrader getHandleRaw() { -+ return (net.minecraft.world.entity.npc.WanderingTrader)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.npc.WanderingTrader getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.npc.WanderingTrader) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java.patch deleted file mode 100644 index 42eaeb3..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -@@ -15,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public Warden getHandleRaw() { -+ return (Warden)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public Warden getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (Warden) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java.patch deleted file mode 100644 index ed98d4d..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWaterMob.java -@@ -10,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public WaterAnimal getHandleRaw() { -+ return (WaterAnimal)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public WaterAnimal getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (WaterAnimal) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java.patch deleted file mode 100644 index da70c98..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWindCharge.java -@@ -10,6 +_,7 @@ - - @Override - public net.minecraft.world.entity.projectile.windcharge.WindCharge getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.windcharge.WindCharge) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java.patch deleted file mode 100644 index de84304..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitch.java -@@ -15,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Witch getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Witch)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Witch getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Witch) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java.patch deleted file mode 100644 index 2c8600b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -@@ -21,8 +_,16 @@ - } - } - -+ // Folia start - region threading -+ @Override -+ public WitherBoss getHandleRaw() { -+ return (WitherBoss)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public WitherBoss getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (WitherBoss) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java.patch deleted file mode 100644 index 3501f85..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWitherSkull.java -@@ -18,8 +_,16 @@ - return this.getHandle().isDangerous(); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.projectile.WitherSkull getHandleRaw() { -+ return (net.minecraft.world.entity.projectile.WitherSkull)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.projectile.WitherSkull getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.projectile.WitherSkull) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java.patch deleted file mode 100644 index ceba85b..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -@@ -30,8 +_,16 @@ - } - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.animal.Wolf getHandleRaw() { -+ return (net.minecraft.world.entity.animal.Wolf)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.animal.Wolf getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.animal.Wolf) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java.patch deleted file mode 100644 index df6cac7..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZoglin.java -@@ -19,8 +_,16 @@ - this.getHandle().setBaby(flag); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Zoglin getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Zoglin)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Zoglin getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Zoglin) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java.patch deleted file mode 100644 index f20758c..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -@@ -12,8 +_,16 @@ - super(server, entity); - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.entity.monster.Zombie getHandleRaw() { -+ return (net.minecraft.world.entity.monster.Zombie)this.entity; -+ } -+ // Folia end - region threading -+ - @Override - public net.minecraft.world.entity.monster.Zombie getHandle() { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading - return (net.minecraft.world.entity.monster.Zombie) this.entity; - } - diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch deleted file mode 100644 index 1f8bb18..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java.patch +++ /dev/null @@ -1,29 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -951,7 +_,7 @@ - return CraftEventFactory.handleBlockSpreadEvent(world, source, target, block, 2); - } - -- public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. -+ public static final ThreadLocal sourceBlockOverrideRT = new ThreadLocal<>(); // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // Folia - region threading - - public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) { - // Suppress during worldgen -@@ -963,7 +_,7 @@ - CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag); - state.setData(block); - -- BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state); -+ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), state); // Folia - region threading - Bukkit.getPluginManager().callEvent(event); - - if (!event.isCancelled()) { -@@ -2232,7 +_,7 @@ - CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); -- if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { -+ if (!net.minecraft.world.level.block.DispenserBlock.eventFired.get().booleanValue()) { // Folia - region threading - if (!event.callEvent()) { - return itemStack; - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch deleted file mode 100644 index baa45c9..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -514,6 +_,7 @@ - } - - protected CraftTask handle(final CraftTask task, final long delay) { // Paper -+ if (true) throw new UnsupportedOperationException(); // Folia - region threading - // Paper start - if (!this.isAsyncScheduler && !task.isSync()) { - this.asyncScheduler.handle(task, delay); diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java.patch deleted file mode 100644 index f1229aa..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java -@@ -45,6 +_,7 @@ - } - @Override - public CraftObjective registerNewObjective(String name, Criteria criteria, net.kyori.adventure.text.Component displayName, RenderType renderType) throws IllegalArgumentException { -+ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet - if (displayName == null) { - displayName = net.kyori.adventure.text.Component.empty(); - } -@@ -204,6 +_,7 @@ - - @Override - public Team registerNewTeam(String name) { -+ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet - Preconditions.checkArgument(name != null, "Team name cannot be null"); - Preconditions.checkArgument(name.length() <= Short.MAX_VALUE, "Team name '%s' is longer than the limit of 32767 characters (%s)", name, name.length()); - Preconditions.checkArgument(this.board.getPlayerTeam(name) == null, "Team name '%s' is already in use", name); -@@ -231,6 +_,7 @@ - - @Override - public void clearSlot(DisplaySlot slot) { -+ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet - Preconditions.checkArgument(slot != null, "Slot cannot be null"); - this.board.setDisplayObjective(CraftScoreboardTranslations.fromBukkitSlot(slot), null); - } diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java.patch deleted file mode 100644 index 6ac7bad..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java.patch +++ /dev/null @@ -1,18 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -@@ -42,6 +_,7 @@ - - @Override - public CraftScoreboard getNewScoreboard() { -+ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet - org.spigotmc.AsyncCatcher.catchOp("scoreboard creation"); // Spigot - CraftScoreboard scoreboard = new CraftScoreboard(new ServerScoreboard(this.server)); - // Paper start -@@ -68,6 +_,7 @@ - - // CraftBukkit method - public void setPlayerBoard(CraftPlayer player, org.bukkit.scoreboard.Scoreboard bukkitScoreboard) { -+ if (true) throw new UnsupportedOperationException(); // Folia - not supported yet - Preconditions.checkArgument(bukkitScoreboard instanceof CraftScoreboard, "Cannot set player scoreboard to an unregistered Scoreboard"); - - CraftScoreboard scoreboard = (CraftScoreboard) bukkitScoreboard; diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java.patch deleted file mode 100644 index 5b109a2..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -379,6 +_,12 @@ - throw new InvalidPluginException("Unsupported API version " + pdf.getAPIVersion()); - } - -+ // Folia start - block plugins not marked as supported -+ if (!pdf.isFoliaSupported()) { -+ throw new InvalidPluginException("Plugin " + pdf.getFullName() + " is not marked as supporting regionised multithreading"); -+ } -+ // Folia end - block plugins not marked as supported -+ - if (toCheck.isOlderThan(minimumVersion)) { - // Older than supported - throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); diff --git a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java.patch b/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java.patch deleted file mode 100644 index 02f7781..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java.patch +++ /dev/null @@ -1,21 +0,0 @@ ---- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -@@ -66,6 +_,13 @@ - this.handle = worldAccess; - } - -+ // Folia start - region threading -+ @Override -+ public net.minecraft.world.level.StructureManager structureManager() { -+ return this.handle.structureManager(); -+ } -+ // Folia end - region threading -+ - public WorldGenLevel getHandle() { - return this.handle; - } -@@ -812,4 +_,3 @@ - } - // Paper end - } -- diff --git a/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotCommand.java.patch b/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotCommand.java.patch deleted file mode 100644 index cb0e892..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotCommand.java.patch +++ /dev/null @@ -1,18 +0,0 @@ ---- a/src/main/java/org/spigotmc/SpigotCommand.java -+++ b/src/main/java/org/spigotmc/SpigotCommand.java -@@ -35,6 +_,7 @@ - .build() - ); - -+ io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading - MinecraftServer console = MinecraftServer.getServer(); - org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); - for (ServerLevel world : console.getAllLevels()) { -@@ -43,6 +_,7 @@ - console.server.reloadCount++; - - Command.broadcastCommandMessage(sender, text("Reload complete.", NamedTextColor.GREEN)); -+ }); // Folia - region threading - } - - return true; diff --git a/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotConfig.java.patch b/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotConfig.java.patch deleted file mode 100644 index 642c402..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotConfig.java.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/src/main/java/org/spigotmc/SpigotConfig.java -+++ b/src/main/java/org/spigotmc/SpigotConfig.java -@@ -182,7 +_,7 @@ - SpigotConfig.restartOnCrash = SpigotConfig.getBoolean("settings.restart-on-crash", SpigotConfig.restartOnCrash); - SpigotConfig.restartScript = SpigotConfig.getString("settings.restart-script", SpigotConfig.restartScript); - SpigotConfig.restartMessage = SpigotConfig.transform(SpigotConfig.getString("messages.restart", "Server is restarting")); -- SpigotConfig.commands.put("restart", new RestartCommand("restart")); -+ //SpigotConfig.commands.put("restart", new RestartCommand("restart")); // Folia - region threading - } - - public static boolean bungee; -@@ -228,7 +_,7 @@ - } - - private static void tpsCommand() { -- SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps")); -+ //SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps")); // Folia - region threading - } - - public static int playerSample; diff --git a/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotWorldConfig.java.patch b/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotWorldConfig.java.patch deleted file mode 100644 index 4437f16..0000000 --- a/folia-server/paper-patches/files/src/main/java/org/spigotmc/SpigotWorldConfig.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -401,7 +_,7 @@ - this.otherMultiplier = (float) this.getDouble("hunger.other-multiplier", 0.0); - } - -- public int currentPrimedTnt = 0; -+ //public int currentPrimedTnt = 0; // Folia - region threading - moved to regionised world data - public int maxTntTicksPerTick; - private void maxTntPerTick() { - if (SpigotConfig.version < 7) { diff --git a/folia-server/paper-patches/files/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java.patch b/folia-server/paper-patches/files/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java.patch deleted file mode 100644 index ca30437..0000000 --- a/folia-server/paper-patches/files/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java.patch +++ /dev/null @@ -1,16 +0,0 @@ ---- a/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java -+++ b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java -@@ -20,6 +_,13 @@ - this.identifier = identifier; - } - -+ // Folia start - region threading -+ @Override -+ public boolean isFoliaSupported() { -+ return true; -+ } -+ // Folia end - region threading -+ - @Override - public @NotNull String getName() { - return this.identifier;