mirror of
https://github.com/Xiao-MoMi/Custom-Crops.git
synced 2025-12-22 16:39:36 +00:00
3.6.8
This commit is contained in:
@@ -394,7 +394,7 @@ public class CropBlock extends AbstractCustomCropsBlock {
|
||||
}
|
||||
}
|
||||
}, bukkitLocation);
|
||||
}, plugin.getScheduler().async());
|
||||
}, world.scheduler().async());
|
||||
}
|
||||
|
||||
public int point(CustomCropsBlockState state) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user