mirror of
https://github.com/Xiao-MoMi/Custom-Crops.git
synced 2025-12-27 19:09:09 +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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user