From 60d2525fbb943910a27b91223fbe681d2565fc14 Mon Sep 17 00:00:00 2001 From: XiaoMoMi <70987828+Xiao-MoMi@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:26:35 +0800 Subject: [PATCH] 3.6.8 --- .../customcrops/api/core/block/CropBlock.java | 2 +- .../api/core/world/CustomCropsWorld.java | 18 ++- .../api/core/world/CustomCropsWorldImpl.java | 33 +++- .../api/core/world/WorldScheduler.java | 144 ++++++++++++++++++ .../customcrops/api/event/CropBreakEvent.java | 2 - .../customcrops/api/util/TagUtils.java | 5 +- .../itemsadder_r1/ItemsAdderListener.java | 3 +- .../worldedit/WorldEditListener.java | 1 - gradle.properties | 10 +- .../command/feature/DebugWorldsCommand.java | 1 + .../command/feature/ForceTickCommand.java | 2 +- .../adaptor/BukkitWorldAdaptor.java | 6 + .../bukkit/world/BukkitWorldManager.java | 12 +- 13 files changed, 215 insertions(+), 24 deletions(-) create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/WorldScheduler.java diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CropBlock.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropBlock.java index f2d618f..1e38e7d 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/core/block/CropBlock.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropBlock.java @@ -394,7 +394,7 @@ public class CropBlock extends AbstractCustomCropsBlock { } } }, bukkitLocation); - }, plugin.getScheduler().async()); + }, world.scheduler().async()); } public int point(CustomCropsBlockState state) { diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorld.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorld.java index 43fbc3c..138d524 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorld.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorld.java @@ -156,6 +156,13 @@ public interface CustomCropsWorld { */ CustomCropsChunk[] lazyChunks(); + /** + * Gets all the loaded regions in this world. + * + * @return An array of {@link CustomCropsRegion} representing the loaded regions. + */ + CustomCropsRegion[] loadedRegions(); + /** * Gets the block state at a specific location. * @@ -186,8 +193,10 @@ public interface CustomCropsWorld { /** * Saves the world data to a file. + * + * @param async async or not */ - void save(); + void save(boolean async); /** * Sets whether the ticking task is ongoing. @@ -301,5 +310,12 @@ public interface CustomCropsWorld { */ @NotNull CustomCropsRegion getOrCreateRegion(RegionPos regionPos); + + /** + * Get the scheduler for this world + * + * @return the scheduler + */ + WorldScheduler scheduler(); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorldImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorldImpl.java index 6c06d70..b8c66e0 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorldImpl.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorldImpl.java @@ -52,6 +52,7 @@ public class CustomCropsWorldImpl implements CustomCropsWorld { private WorldSetting setting; private final WorldAdaptor adaptor; private final WorldExtraData extraData; + private final WorldScheduler scheduler; public CustomCropsWorldImpl(W world, WorldAdaptor adaptor) { this.world = new WeakReference<>(world); @@ -61,6 +62,7 @@ public class CustomCropsWorldImpl implements CustomCropsWorld { this.adaptor = adaptor; this.extraData = adaptor.loadExtraData(world); this.currentMinecraftDay = (int) (bukkitWorld().getFullTime() / 24_000); + this.scheduler = new WorldScheduler(BukkitCustomCropsPlugin.getInstance()); } @NotNull @@ -140,6 +142,11 @@ public class CustomCropsWorldImpl implements CustomCropsWorld { return lazyChunks.values().toArray(new CustomCropsChunk[0]); } + @Override + public CustomCropsRegion[] loadedRegions() { + return loadedRegions.values().toArray(new CustomCropsRegion[0]); + } + @NotNull @Override public Optional getBlockState(Pos3 location) { @@ -181,7 +188,15 @@ public class CustomCropsWorldImpl implements CustomCropsWorld { } @Override - public void save() { + public void save(boolean async) { + if (async) { + this.scheduler.async().execute(this::save); + } else { + BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run(this::save, null); + } + } + + private void save() { long time1 = System.currentTimeMillis(); this.adaptor.saveExtraData(this); for (CustomCropsChunk chunk : loadedChunks.values()) { @@ -201,7 +216,7 @@ public class CustomCropsWorldImpl implements CustomCropsWorld { public void setTicking(boolean tick) { if (tick) { if (this.tickTask == null || this.tickTask.isCancelled()) - this.tickTask = BukkitCustomCropsPlugin.getInstance().getScheduler().asyncRepeating(this::timer, 1, 1, TimeUnit.SECONDS); + this.tickTask = this.scheduler.asyncRepeating(this::timer, 1, 1, TimeUnit.SECONDS); } else { if (this.tickTask != null && !this.tickTask.isCancelled()) this.tickTask.cancel(); @@ -257,7 +272,8 @@ public class CustomCropsWorldImpl implements CustomCropsWorld { private void saveLazyRegions() { this.regionTimer++; - if (this.regionTimer >= 600) { + // To avoid the same timing as saving + if (this.regionTimer >= 666) { this.regionTimer = 0; ArrayList removed = new ArrayList<>(); for (Map.Entry entry : loadedRegions.entrySet()) { @@ -461,11 +477,12 @@ public class CustomCropsWorldImpl implements CustomCropsWorld { } private boolean shouldUnloadRegion(RegionPos regionPos) { + World bukkitWorld = bukkitWorld(); for (int chunkX = regionPos.x() * 32; chunkX < regionPos.x() * 32 + 32; chunkX++) { for (int chunkZ = regionPos.z() * 32; chunkZ < regionPos.z() * 32 + 32; chunkZ++) { // if a chunk is unloaded, then it should not be in the loaded chunks map ChunkPos pos = ChunkPos.of(chunkX, chunkZ); - if (isChunkLoaded(pos) || this.lazyChunks.containsKey(pos)) { + if (isChunkLoaded(pos) || this.lazyChunks.containsKey(pos) || bukkitWorld.isChunkLoaded(chunkX, chunkZ)) { return false; } } @@ -492,8 +509,14 @@ public class CustomCropsWorldImpl implements CustomCropsWorld { } } } - this.loadedRegions.remove(region.regionPos()); this.adaptor.saveRegion(this, region); + this.loadedRegions.remove(region.regionPos()); + BukkitCustomCropsPlugin.getInstance().debug(() -> "[" + worldName + "] " + "Region " + region.regionPos() + " unloaded."); return true; } + + @Override + public WorldScheduler scheduler() { + return scheduler; + } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldScheduler.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldScheduler.java new file mode 100644 index 0000000..97b1107 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldScheduler.java @@ -0,0 +1,144 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customcrops.api.core.world; + +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Arrays; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class WorldScheduler { + private static final int PARALLELISM = 1; + + private final CustomCropsPlugin plugin; + + private final ScheduledThreadPoolExecutor scheduler; + private final ForkJoinPool worker; + + public WorldScheduler(CustomCropsPlugin plugin) { + this.plugin = plugin; + + this.scheduler = new ScheduledThreadPoolExecutor(1, r -> { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setName("customcrops-world-scheduler"); + return thread; + }); + this.scheduler.setRemoveOnCancelPolicy(true); + this.scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.worker = new ForkJoinPool(PARALLELISM, new WorkerThreadFactory(), new ExceptionHandler(), false); + } + + public Executor async() { + return this.worker; + } + + public SchedulerTask asyncLater(Runnable task, long delay, TimeUnit unit) { + ScheduledFuture future = this.scheduler.schedule(() -> this.worker.execute(task), delay, unit); + return new JavaCancellable(future); + } + + public SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit) { + ScheduledFuture future = this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(task), delay, interval, unit); + return new JavaCancellable(future); + } + + public void shutdownScheduler() { + this.scheduler.shutdown(); + try { + if (!this.scheduler.awaitTermination(1, TimeUnit.MINUTES)) { + this.plugin.getPluginLogger().severe("Timed out waiting for the CustomCrops scheduler to terminate"); + reportRunningTasks(thread -> thread.getName().equals("customcrops-world-scheduler")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void shutdownExecutor() { + this.worker.shutdown(); + try { + if (!this.worker.awaitTermination(1, TimeUnit.MINUTES)) { + this.plugin.getPluginLogger().severe("Timed out waiting for the CustomCrops worker thread pool to terminate"); + reportRunningTasks(thread -> thread.getName().startsWith("customcrops-world-worker-")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private void reportRunningTasks(Predicate predicate) { + Thread.getAllStackTraces().forEach((thread, stack) -> { + if (predicate.test(thread)) { + this.plugin.getPluginLogger().warn("Thread " + thread.getName() + " is blocked, and may be the reason for the slow shutdown!\n" + + Arrays.stream(stack).map(el -> " " + el).collect(Collectors.joining("\n")) + ); + } + }); + } + + private static final class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { + private static final AtomicInteger COUNT = new AtomicInteger(0); + + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + thread.setDaemon(true); + thread.setName("customcrops-world-worker-" + COUNT.getAndIncrement()); + return thread; + } + } + + private final class ExceptionHandler implements UncaughtExceptionHandler { + @Override + public void uncaughtException(Thread t, Throwable e) { + WorldScheduler.this.plugin.getPluginLogger().warn("Thread " + t.getName() + " threw an uncaught exception", e); + } + } + + public static class JavaCancellable implements SchedulerTask { + + private final ScheduledFuture future; + + public JavaCancellable(ScheduledFuture future) { + this.future = future; + } + + @Override + public void cancel() { + this.future.cancel(false); + } + + @Override + public boolean isCancelled() { + return future.isCancelled(); + } + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/CropBreakEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/CropBreakEvent.java index c068aa4..3fae114 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/CropBreakEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/CropBreakEvent.java @@ -17,9 +17,7 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.core.BuiltInBlockMechanics; import net.momirealms.customcrops.api.core.block.BreakReason; -import net.momirealms.customcrops.api.core.block.CropBlock; import net.momirealms.customcrops.api.core.mechanic.crop.CropConfig; import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/TagUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/TagUtils.java index 6df2894..55d6a80 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/util/TagUtils.java +++ b/api/src/main/java/net/momirealms/customcrops/api/util/TagUtils.java @@ -17,7 +17,10 @@ package net.momirealms.customcrops.api.util; -import com.flowpowered.nbt.*; +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.Tag; +import com.flowpowered.nbt.TagType; import com.flowpowered.nbt.stream.NBTInputStream; import com.flowpowered.nbt.stream.NBTOutputStream; diff --git a/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderListener.java b/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderListener.java index 435e047..822325f 100644 --- a/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderListener.java +++ b/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderListener.java @@ -65,7 +65,8 @@ public class ItemsAdderListener extends AbstractCustomEventListener { itemManager.handlePlayerInteractBlock( event.getPlayer(), event.getBlockClicked(), - event.getNamespacedID(), event.getBlockFace(), + event.getNamespacedID(), + event.getBlockFace(), event.getHand(), event.getItem(), event diff --git a/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/worldedit/WorldEditListener.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/worldedit/WorldEditListener.java index 4afff31..d07e453 100644 --- a/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/worldedit/WorldEditListener.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/worldedit/WorldEditListener.java @@ -39,7 +39,6 @@ import net.momirealms.customcrops.api.core.ConfigManager; import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import net.momirealms.customcrops.api.core.world.CustomCropsWorld; import net.momirealms.customcrops.api.core.world.Pos3; -import net.momirealms.customcrops.api.util.PluginUtils; import net.momirealms.customcrops.common.plugin.feature.Reloadable; import org.bukkit.Bukkit; import org.bukkit.entity.Player; diff --git a/gradle.properties b/gradle.properties index 9b7ff6d..30b469d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=3.6.7 +project_version=3.6.8 config_version=39 project_group=net.momirealms @@ -18,10 +18,10 @@ adventure_platform_version=4.3.4 sparrow_heart_version=0.43 cloud_core_version=2.0.0 cloud_services_version=2.0.0 -cloud_brigadier_version=2.0.0-beta.9 -cloud_bukkit_version=2.0.0-beta.9 -cloud_paper_version=2.0.0-beta.9 -cloud_minecraft_extras_version=2.0.0-beta.9 +cloud_brigadier_version=2.0.0-beta.10 +cloud_bukkit_version=2.0.0-beta.10 +cloud_paper_version=2.0.0-beta.10 +cloud_minecraft_extras_version=2.0.0-beta.10 boosted_yaml_version=1.3.7 byte_buddy_version=1.14.18 mojang_brigadier_version=1.0.18 diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/DebugWorldsCommand.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/DebugWorldsCommand.java index 8ea00fc..14a8a84 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/DebugWorldsCommand.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/DebugWorldsCommand.java @@ -42,6 +42,7 @@ public class DebugWorldsCommand extends BukkitCommandFeature { for (World world : Bukkit.getWorlds()) { BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(world).ifPresent(w -> { sender.sendMessage(AdventureHelper.miniMessage("World: " + world.getName() + "")); + sender.sendMessage(AdventureHelper.miniMessage(" - Loaded regions: " + w.loadedRegions().length)); sender.sendMessage(AdventureHelper.miniMessage(" - Loaded chunks: " + w.loadedChunks().length)); sender.sendMessage(AdventureHelper.miniMessage(" - Lazy chunks: " + w.lazyChunks().length)); }); diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/ForceTickCommand.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/ForceTickCommand.java index af658e8..2225c8a 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/ForceTickCommand.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/ForceTickCommand.java @@ -87,7 +87,7 @@ public class ForceTickCommand extends BukkitCommandFeature { return; } CustomCropsWorld customCropsWorld = optionalWorld.get(); - BukkitCustomCropsPlugin.getInstance().getScheduler().async().execute(() -> { + customCropsWorld.scheduler().async().execute(() -> { int amount = 0; long time1 = System.currentTimeMillis(); for (CustomCropsChunk customCropsChunk : customCropsWorld.loadedChunks()) { diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/BukkitWorldAdaptor.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/BukkitWorldAdaptor.java index 82501ac..fc5ab1f 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/BukkitWorldAdaptor.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/BukkitWorldAdaptor.java @@ -184,6 +184,12 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor { @Override public void saveRegion(CustomCropsWorld world, CustomCropsRegion region) { File file = getRegionDataFile(world.world(), region.regionPos()); + if (region.canPrune()) { + if (file.exists()) { + file.delete(); + } + return; + } long time1 = System.currentTimeMillis(); File parentDir = file.getParentFile(); if (parentDir != null && !parentDir.exists()) { diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java index bfe2798..ee0ee5f 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java @@ -209,13 +209,11 @@ public class BukkitWorldManager implements WorldManager, Listener { if (world.isChunkLoaded(pos)) return; Optional customChunk = world.getChunk(pos); // don't load bukkit chunk again since it has been loaded - customChunk.ifPresent(customCropsChunk -> { - customCropsChunk.load(false); - }); + customChunk.ifPresent(customCropsChunk -> customCropsChunk.load(false)); } public void notifyOfflineUpdates(CustomCropsWorld world, ChunkPos pos) { - world.getChunk(pos).ifPresent(CustomCropsChunk::notifyOfflineTask); + world.getLoadedChunk(pos).ifPresent(CustomCropsChunk::notifyOfflineTask); } @Override @@ -225,14 +223,16 @@ public class BukkitWorldManager implements WorldManager, Listener { return false; } removedWorld.setTicking(false); - removedWorld.save(); + removedWorld.save(false); + removedWorld.scheduler().shutdownScheduler(); + removedWorld.scheduler().shutdownExecutor(); return true; } @EventHandler public void onWorldSave(WorldSaveEvent event) { final World world = event.getWorld(); - getWorld(world).ifPresent(CustomCropsWorld::save); + getWorld(world).ifPresent(world1 -> world1.save(true)); } @EventHandler (priority = EventPriority.HIGH)