mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-07-01 15:36:45 +00:00
use scheduled thread pool instead of loopers for the EngineSVC
This commit is contained in:
parent
adb7188eb9
commit
2c60192de3
@ -155,6 +155,7 @@ public class IrisSettings {
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class IrisSettingsPerformance {
|
public static class IrisSettingsPerformance {
|
||||||
|
private IrisSettingsEngineSVC engineSVC = new IrisSettingsEngineSVC();
|
||||||
public boolean trimMantleInStudio = false;
|
public boolean trimMantleInStudio = false;
|
||||||
public int mantleKeepAlive = 30;
|
public int mantleKeepAlive = 30;
|
||||||
public int cacheSize = 4_096;
|
public int cacheSize = 4_096;
|
||||||
@ -242,4 +243,14 @@ public class IrisSettings {
|
|||||||
public boolean disableTimeAndWeather = true;
|
public boolean disableTimeAndWeather = true;
|
||||||
public boolean autoStartDefaultStudio = false;
|
public boolean autoStartDefaultStudio = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class IrisSettingsEngineSVC {
|
||||||
|
public boolean useVirtualThreads = true;
|
||||||
|
public int priority = Thread.NORM_PRIORITY;
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return Math.max(Math.min(priority, Thread.MAX_PRIORITY), Thread.MIN_PRIORITY);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,67 +4,72 @@ import com.google.common.util.concurrent.AtomicDouble;
|
|||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
import com.volmit.iris.core.IrisSettings;
|
import com.volmit.iris.core.IrisSettings;
|
||||||
import com.volmit.iris.core.loader.IrisData;
|
import com.volmit.iris.core.loader.IrisData;
|
||||||
import com.volmit.iris.core.nms.container.Pair;
|
|
||||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
|
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
|
||||||
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.format.C;
|
import com.volmit.iris.util.format.C;
|
||||||
import com.volmit.iris.util.format.Form;
|
import com.volmit.iris.util.format.Form;
|
||||||
|
import com.volmit.iris.util.math.RNG;
|
||||||
import com.volmit.iris.util.plugin.IrisService;
|
import com.volmit.iris.util.plugin.IrisService;
|
||||||
import com.volmit.iris.util.plugin.VolmitSender;
|
import com.volmit.iris.util.plugin.VolmitSender;
|
||||||
import com.volmit.iris.util.scheduling.Looper;
|
import com.volmit.iris.util.scheduling.Looper;
|
||||||
|
import lombok.Synchronized;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.world.WorldLoadEvent;
|
import org.bukkit.event.world.WorldLoadEvent;
|
||||||
import org.bukkit.event.world.WorldUnloadEvent;
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class IrisEngineSVC implements IrisService {
|
public class IrisEngineSVC implements IrisService {
|
||||||
private final AtomicInteger tectonicLimit = new AtomicInteger(30);
|
private final AtomicInteger tectonicLimit = new AtomicInteger(30);
|
||||||
private final AtomicInteger tectonicPlates = new AtomicInteger();
|
private final AtomicInteger tectonicPlates = new AtomicInteger();
|
||||||
private final AtomicInteger queuedTectonicPlates = new AtomicInteger();
|
private final AtomicInteger queuedTectonicPlates = new AtomicInteger();
|
||||||
|
private final AtomicInteger trimmerAlive = new AtomicInteger();
|
||||||
|
private final AtomicInteger unloaderAlive = new AtomicInteger();
|
||||||
|
private final AtomicInteger totalWorlds = new AtomicInteger();
|
||||||
private final AtomicDouble maxIdleDuration = new AtomicDouble();
|
private final AtomicDouble maxIdleDuration = new AtomicDouble();
|
||||||
private final AtomicDouble minIdleDuration = new AtomicDouble();
|
private final AtomicDouble minIdleDuration = new AtomicDouble();
|
||||||
private final AtomicLong loadedChunks = new AtomicLong();
|
private final AtomicLong loadedChunks = new AtomicLong();
|
||||||
private final List<Pair<World, PlatformChunkGenerator>> worlds = new CopyOnWriteArrayList<>();
|
private final KMap<World, Registered> worlds = new KMap<>();
|
||||||
private Looper trimTicker;
|
private ScheduledExecutorService service;
|
||||||
private Looper unloadTicker;
|
|
||||||
private Looper updateTicker;
|
private Looper updateTicker;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
tectonicLimit.set(IrisSettings.get().getPerformance().getTectonicPlateSize());
|
var settings = IrisSettings.get().getPerformance();
|
||||||
for (World world : Bukkit.getWorlds()) {
|
var engine = settings.getEngineSVC();
|
||||||
var access = IrisToolbelt.access(world);
|
service = Executors.newScheduledThreadPool(0,
|
||||||
if (access == null) return;
|
(engine.isUseVirtualThreads()
|
||||||
worlds.add(new Pair<>(world, access));
|
? Thread.ofVirtual()
|
||||||
}
|
: Thread.ofPlatform().priority(engine.getPriority()))
|
||||||
|
.name("Iris EngineSVC-", 0)
|
||||||
trimLogic();
|
.factory());
|
||||||
unloadLogic();
|
tectonicLimit.set(settings.getTectonicPlateSize());
|
||||||
|
Bukkit.getWorlds().forEach(this::add);
|
||||||
setup();
|
setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
|
service.shutdown();
|
||||||
updateTicker.interrupt();
|
updateTicker.interrupt();
|
||||||
trimTicker.interrupt();
|
worlds.keySet().forEach(this::remove);
|
||||||
unloadTicker.interrupt();
|
|
||||||
worlds.clear();
|
worlds.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void engineStatus(VolmitSender sender) {
|
public void engineStatus(VolmitSender sender) {
|
||||||
sender.sendMessage(C.DARK_PURPLE + "-------------------------");
|
sender.sendMessage(C.DARK_PURPLE + "-------------------------");
|
||||||
sender.sendMessage(C.DARK_PURPLE + "Status:");
|
sender.sendMessage(C.DARK_PURPLE + "Status:");
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Trim: " + C.LIGHT_PURPLE + trimTicker.isAlive());
|
sender.sendMessage(C.DARK_PURPLE + "- Service: " + C.LIGHT_PURPLE + (service.isShutdown() ? "Shutdown" : "Running"));
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Unload: " + C.LIGHT_PURPLE + unloadTicker.isAlive());
|
sender.sendMessage(C.DARK_PURPLE + "- Updater: " + C.LIGHT_PURPLE + (updateTicker.isAlive() ? "Running" : "Stopped"));
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Update: " + C.LIGHT_PURPLE + updateTicker.isAlive());
|
sender.sendMessage(C.DARK_PURPLE + "- Trimmers: " + C.LIGHT_PURPLE + trimmerAlive.get());
|
||||||
|
sender.sendMessage(C.DARK_PURPLE + "- Unloaders: " + C.LIGHT_PURPLE + unloaderAlive.get());
|
||||||
sender.sendMessage(C.DARK_PURPLE + "Tectonic Plates:");
|
sender.sendMessage(C.DARK_PURPLE + "Tectonic Plates:");
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Limit: " + C.LIGHT_PURPLE + tectonicLimit.get());
|
sender.sendMessage(C.DARK_PURPLE + "- Limit: " + C.LIGHT_PURPLE + tectonicLimit.get());
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Total: " + C.LIGHT_PURPLE + tectonicPlates.get());
|
sender.sendMessage(C.DARK_PURPLE + "- Total: " + C.LIGHT_PURPLE + tectonicPlates.get());
|
||||||
@ -72,7 +77,7 @@ public class IrisEngineSVC implements IrisService {
|
|||||||
sender.sendMessage(C.DARK_PURPLE + "- Max Idle Duration: " + C.LIGHT_PURPLE + Form.duration(maxIdleDuration.get(), 2));
|
sender.sendMessage(C.DARK_PURPLE + "- Max Idle Duration: " + C.LIGHT_PURPLE + Form.duration(maxIdleDuration.get(), 2));
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Min Idle Duration: " + C.LIGHT_PURPLE + Form.duration(minIdleDuration.get(), 2));
|
sender.sendMessage(C.DARK_PURPLE + "- Min Idle Duration: " + C.LIGHT_PURPLE + Form.duration(minIdleDuration.get(), 2));
|
||||||
sender.sendMessage(C.DARK_PURPLE + "Other:");
|
sender.sendMessage(C.DARK_PURPLE + "Other:");
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Iris Worlds: " + C.LIGHT_PURPLE + worlds.size());
|
sender.sendMessage(C.DARK_PURPLE + "- Iris Worlds: " + C.LIGHT_PURPLE + totalWorlds.get());
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Loaded Chunks: " + C.LIGHT_PURPLE + loadedChunks.get());
|
sender.sendMessage(C.DARK_PURPLE + "- Loaded Chunks: " + C.LIGHT_PURPLE + loadedChunks.get());
|
||||||
sender.sendMessage(C.DARK_PURPLE + "- Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize()));
|
sender.sendMessage(C.DARK_PURPLE + "- Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize()));
|
||||||
sender.sendMessage(C.DARK_PURPLE + "-------------------------");
|
sender.sendMessage(C.DARK_PURPLE + "-------------------------");
|
||||||
@ -80,15 +85,24 @@ public class IrisEngineSVC implements IrisService {
|
|||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onWorldUnload(WorldUnloadEvent event) {
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
worlds.removeIf(p -> p.getA() == event.getWorld());
|
remove(event.getWorld());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onWorldLoad(WorldLoadEvent event) {
|
public void onWorldLoad(WorldLoadEvent event) {
|
||||||
var world = event.getWorld();
|
add(event.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(World world) {
|
||||||
|
var entry = worlds.remove(world);
|
||||||
|
if (entry == null) return;
|
||||||
|
entry.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(World world) {
|
||||||
var access = IrisToolbelt.access(world);
|
var access = IrisToolbelt.access(world);
|
||||||
if (access == null) return;
|
if (access == null) return;
|
||||||
worlds.add(new Pair<>(world, access));
|
worlds.put(world, new Registered(world.getName(), access));
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void setup() {
|
private synchronized void setup() {
|
||||||
@ -102,16 +116,26 @@ public class IrisEngineSVC implements IrisService {
|
|||||||
queuedTectonicPlates.set(0);
|
queuedTectonicPlates.set(0);
|
||||||
tectonicPlates.set(0);
|
tectonicPlates.set(0);
|
||||||
loadedChunks.set(0);
|
loadedChunks.set(0);
|
||||||
|
unloaderAlive.set(0);
|
||||||
|
trimmerAlive.set(0);
|
||||||
|
totalWorlds.set(0);
|
||||||
|
|
||||||
double maxDuration = Long.MIN_VALUE;
|
double maxDuration = Long.MIN_VALUE;
|
||||||
double minDuration = Long.MAX_VALUE;
|
double minDuration = Long.MAX_VALUE;
|
||||||
for (var pair : worlds) {
|
for (var entry : worlds.entrySet()) {
|
||||||
var engine = pair.getB().getEngine();
|
var registered = entry.getValue();
|
||||||
|
if (registered.closed) continue;
|
||||||
|
|
||||||
|
totalWorlds.incrementAndGet();
|
||||||
|
unloaderAlive.addAndGet(registered.unloaderAlive() ? 1 : 0);
|
||||||
|
trimmerAlive.addAndGet(registered.trimmerAlive() ? 1 : 0);
|
||||||
|
|
||||||
|
var engine = registered.getEngine();
|
||||||
if (engine == null) continue;
|
if (engine == null) continue;
|
||||||
|
|
||||||
queuedTectonicPlates.addAndGet((int) engine.getMantle().getUnloadRegionCount());
|
queuedTectonicPlates.addAndGet((int) engine.getMantle().getUnloadRegionCount());
|
||||||
tectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount());
|
tectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount());
|
||||||
loadedChunks.addAndGet(pair.getA().getLoadedChunks().length);
|
loadedChunks.addAndGet(entry.getKey().getLoadedChunks().length);
|
||||||
|
|
||||||
double duration = engine.getMantle().getAdjustedIdleDuration();
|
double duration = engine.getMantle().getAdjustedIdleDuration();
|
||||||
if (duration > maxDuration) maxDuration = duration;
|
if (duration > maxDuration) maxDuration = duration;
|
||||||
@ -120,25 +144,7 @@ public class IrisEngineSVC implements IrisService {
|
|||||||
maxIdleDuration.set(maxDuration);
|
maxIdleDuration.set(maxDuration);
|
||||||
minIdleDuration.set(minDuration);
|
minIdleDuration.set(minDuration);
|
||||||
|
|
||||||
if (!trimTicker.isAlive()) {
|
worlds.values().forEach(Registered::update);
|
||||||
Iris.error("TrimTicker found dead! Booting it up!");
|
|
||||||
try {
|
|
||||||
trimLogic();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Iris.error("What happened?");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!unloadTicker.isAlive()) {
|
|
||||||
Iris.error("UnloadTicker found dead! Booting it up!");
|
|
||||||
try {
|
|
||||||
unloadLogic();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Iris.error("What happened?");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@ -148,98 +154,89 @@ public class IrisEngineSVC implements IrisService {
|
|||||||
updateTicker.start();
|
updateTicker.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void trimLogic() {
|
private final class Registered {
|
||||||
if (trimTicker != null && trimTicker.isAlive())
|
private final String name;
|
||||||
return;
|
private final PlatformChunkGenerator access;
|
||||||
|
private transient ScheduledFuture<?> trimmer;
|
||||||
|
private transient ScheduledFuture<?> unloader;
|
||||||
|
private transient boolean closed;
|
||||||
|
|
||||||
trimTicker = new Looper() {
|
private Registered(String name, PlatformChunkGenerator access) {
|
||||||
private final Supplier<Engine> supplier = createSupplier();
|
this.name = name;
|
||||||
|
this.access = access;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
private boolean unloaderAlive() {
|
||||||
protected long loop() {
|
return unloader != null && !unloader.isDone() && !unloader.isCancelled();
|
||||||
long start = System.currentTimeMillis();
|
}
|
||||||
try {
|
|
||||||
Engine engine = supplier.get();
|
private boolean trimmerAlive() {
|
||||||
if (engine != null) {
|
return trimmer != null && !trimmer.isDone() && !trimmer.isCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private void update() {
|
||||||
|
if (closed || service == null || service.isShutdown())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (trimmer == null || trimmer.isDone() || trimmer.isCancelled()) {
|
||||||
|
trimmer = service.scheduleAtFixedRate(() -> {
|
||||||
|
Engine engine = getEngine();
|
||||||
|
if (engine == null || !engine.getMantle().getMantle().shouldReduce(engine))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
engine.getMantle().trim(tectonicLimit.get() / worlds.size());
|
engine.getMantle().trim(tectonicLimit.get() / worlds.size());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
Iris.error("EngineSVC: Failed to trim for " + name);
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
}, RNG.r.nextInt(1000), 1000, TimeUnit.MILLISECONDS);
|
||||||
Iris.reportError(e);
|
|
||||||
Iris.error("EngineSVC: Failed to trim.");
|
|
||||||
e.printStackTrace();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int size = worlds.size();
|
|
||||||
long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start);
|
|
||||||
if (time <= 0)
|
|
||||||
return 0;
|
|
||||||
return time;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
trimTicker.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void unloadLogic() {
|
if (unloader == null || unloader.isDone() || unloader.isCancelled()) {
|
||||||
if (unloadTicker != null && unloadTicker.isAlive())
|
unloader = service.scheduleAtFixedRate(() -> {
|
||||||
return;
|
Engine engine = getEngine();
|
||||||
|
if (engine == null || !engine.getMantle().getMantle().shouldReduce(engine))
|
||||||
|
return;
|
||||||
|
|
||||||
unloadTicker = new Looper() {
|
try {
|
||||||
private final Supplier<Engine> supplier = createSupplier();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected long loop() {
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
try {
|
|
||||||
Engine engine = supplier.get();
|
|
||||||
if (engine != null) {
|
|
||||||
long unloadStart = System.currentTimeMillis();
|
long unloadStart = System.currentTimeMillis();
|
||||||
int count = engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / worlds.size());
|
int count = engine.getMantle().unloadTectonicPlate(tectonicLimit.get() / worlds.size());
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2));
|
Iris.debug(C.GOLD + "Unloaded " + C.YELLOW + count + " TectonicPlates in " + C.RED + Form.duration(System.currentTimeMillis() - unloadStart, 2));
|
||||||
}
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.reportError(e);
|
||||||
|
Iris.error("EngineSVC: Failed to unload for " + name);
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
}, RNG.r.nextInt(1000), 1000, TimeUnit.MILLISECONDS);
|
||||||
Iris.reportError(e);
|
|
||||||
Iris.error("EngineSVC: Failed to unload.");
|
|
||||||
e.printStackTrace();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int size = worlds.size();
|
|
||||||
long time = (size > 0 ? 1000 / size : 1000) - (System.currentTimeMillis() - start);
|
|
||||||
if (time <= 0)
|
|
||||||
return 0;
|
|
||||||
return time;
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
unloadTicker.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Supplier<Engine> createSupplier() {
|
@Synchronized
|
||||||
AtomicInteger i = new AtomicInteger();
|
private void close() {
|
||||||
return () -> {
|
if (closed) return;
|
||||||
if (i.get() >= worlds.size()) {
|
closed = true;
|
||||||
i.set(0);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (int j = 0; j < worlds.size(); j++) {
|
|
||||||
var pair = worlds.get(i.getAndIncrement());
|
|
||||||
if (i.get() >= worlds.size()) {
|
|
||||||
i.set(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var engine = pair.getB().getEngine();
|
if (trimmer != null) {
|
||||||
if (engine != null && !engine.isClosed() && engine.getMantle().getMantle().shouldReduce(engine)) {
|
trimmer.cancel(false);
|
||||||
return engine;
|
trimmer = null;
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Iris.error("EngineSVC: Failed to create supplier.");
|
|
||||||
e.printStackTrace();
|
|
||||||
Iris.reportError(e);
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
};
|
if (unloader != null) {
|
||||||
|
unloader.cancel(false);
|
||||||
|
unloader = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Engine getEngine() {
|
||||||
|
if (closed) return null;
|
||||||
|
return access.getEngine();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user