9
0
mirror of https://github.com/Xiao-MoMi/Custom-Crops.git synced 2025-12-22 16:39:36 +00:00
This commit is contained in:
XiaoMoMi
2024-09-13 16:26:35 +08:00
parent 3e251ed47a
commit 60d2525fbb
13 changed files with 215 additions and 24 deletions

View File

@@ -394,7 +394,7 @@ public class CropBlock extends AbstractCustomCropsBlock {
}
}
}, bukkitLocation);
}, plugin.getScheduler().async());
}, world.scheduler().async());
}
public int point(CustomCropsBlockState state) {

View File

@@ -156,6 +156,13 @@ public interface CustomCropsWorld<W> {
*/
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<W> {
/**
* 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<W> {
*/
@NotNull
CustomCropsRegion getOrCreateRegion(RegionPos regionPos);
/**
* Get the scheduler for this world
*
* @return the scheduler
*/
WorldScheduler scheduler();
}

View File

@@ -52,6 +52,7 @@ public class CustomCropsWorldImpl<W> implements CustomCropsWorld<W> {
private WorldSetting setting;
private final WorldAdaptor<W> adaptor;
private final WorldExtraData extraData;
private final WorldScheduler scheduler;
public CustomCropsWorldImpl(W world, WorldAdaptor<W> adaptor) {
this.world = new WeakReference<>(world);
@@ -61,6 +62,7 @@ public class CustomCropsWorldImpl<W> implements CustomCropsWorld<W> {
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<W> implements CustomCropsWorld<W> {
return lazyChunks.values().toArray(new CustomCropsChunk[0]);
}
@Override
public CustomCropsRegion[] loadedRegions() {
return loadedRegions.values().toArray(new CustomCropsRegion[0]);
}
@NotNull
@Override
public Optional<CustomCropsBlockState> getBlockState(Pos3 location) {
@@ -181,7 +188,15 @@ public class CustomCropsWorldImpl<W> implements CustomCropsWorld<W> {
}
@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<W> implements CustomCropsWorld<W> {
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<W> implements CustomCropsWorld<W> {
private void saveLazyRegions() {
this.regionTimer++;
if (this.regionTimer >= 600) {
// To avoid the same timing as saving
if (this.regionTimer >= 666) {
this.regionTimer = 0;
ArrayList<CustomCropsRegion> removed = new ArrayList<>();
for (Map.Entry<RegionPos, CustomCropsRegion> entry : loadedRegions.entrySet()) {
@@ -461,11 +477,12 @@ public class CustomCropsWorldImpl<W> implements CustomCropsWorld<W> {
}
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<W> implements CustomCropsWorld<W> {
}
}
}
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;
}
}

View File

@@ -0,0 +1,144 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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<Thread> 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();
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -42,6 +42,7 @@ public class DebugWorldsCommand extends BukkitCommandFeature<CommandSender> {
for (World world : Bukkit.getWorlds()) {
BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(world).ifPresent(w -> {
sender.sendMessage(AdventureHelper.miniMessage("<gold>World: " + world.getName() + "</gold>"));
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));
});

View File

@@ -87,7 +87,7 @@ public class ForceTickCommand extends BukkitCommandFeature<CommandSender> {
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()) {

View File

@@ -184,6 +184,12 @@ public class BukkitWorldAdaptor extends AbstractWorldAdaptor<World> {
@Override
public void saveRegion(CustomCropsWorld<World> 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()) {

View File

@@ -209,13 +209,11 @@ public class BukkitWorldManager implements WorldManager, Listener {
if (world.isChunkLoaded(pos)) return;
Optional<CustomCropsChunk> 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)